2015年5月11日月曜日

フォトアルバムから画像取得のライブラリ化を試作(本編2+使用例編)

フォトライブラリから画像取得の独自ライブラリを試作した話の続きです。Swiftコードの紹介が途中までだったので、その続きから始めます。

 

2つのTableViewを取り上げたので、残りのUI部品を紹介します。UIViewの右側ブロックに入れる部品で、画像表示用のUIImageViewと、メッセージ欄のUILabelが含まれます。初期化処理から呼ばれて、画面上に表示するためのSwiftコードです。

// ======================================== 他のUI部品
var msgLabel: UILabel!
var imgView: UIImageView!

private func setupUiPartsF() {
    // メッセージ&情報表示用ラベル
    let iX: CGFloat = W_MARGIN_LEFT + W_NAME_TABLE + W_MARGIN_CENTER + W_NAME_TABLE2 + W_MARGIN_CENTER2
    let iY: CGFloat = H_MARGIN_TOP
    let iWidth: CGFloat = w_image
    let iHeight: CGFloat = 25
    msgLabel = zCreateLblF("", 18, ALIGN_CENTER, iX, iY, iWidth, iHeight)
    self.addSubview(msgLabel)
    // 画像表示用ImageView
    let iX2: CGFloat = iX
    let iY2: CGFloat = H_MARGIN_TOP + H_LABEL_W_MARGIN
    let iWidth2: CGFloat = w_image
    let iHeight2: CGFloat = h_image
    imgView = zCreateImgViewF(defaultImg, iX2, iY2, iWidth2, iHeight2)
    imgView.contentMode = UIViewContentMode.ScaleAspectFit // 縦横比保持で最大表示
    self.addSubview(imgView)
}
private func setMsgF(rStr:String) {
    msgLabel.text = rStr
}

他のUI部品と同じように、座標位置と大きさを計算してから、UI部品の生成関数で生成しています。UIImageViewでは、画像を表示する方式も大事です。縦横比を保持したまま最大に表示する方式に設定しました。

メッセージ欄へのアクセスですが、UI部品を直接呼び出す形ではなく、アクセス用の専用関数を付けました。いつも使っている、間接参照によるアクセスです。

 

初期化処理から呼ばれる関数が終わったので、残りの関数に移ります。

今回の肝となる処理の1つでもある、PHAssetからUIImageを生成する関数です。生成する際にはサイズ指定が必要なので、画像の幅と高さも引数に含めています。

// ======================================== PHAssetからUIImageを生成
// 仕様確認の結果:元サイズより拡大することはないようだ(Xcode:6.3.1,iOS-SDK:8.3)
// 仕様確認の結果:指定したサイズは、それぞれ最小サイズの意味らしい。縦横比保持で(500,500)を指定すると(700,500)等で生成される。
// 上記結果からの疑問:オプションの設定値で動作結果が変わるかも。ただし、それらしい設定は見付けられていない。
// SDKのバグ?:サイズ指定の値が小さいと、関係ないサイズで生成される(Xcode:6.3.1,iOS-SDK:8.3)
func convert2ImgF(rPHAsset:PHAsset, _ rWidth:CGFloat, _ rHeight:CGFloat) -> UIImage? {
    if (rWidth < IMG_WIDTH_MIN) { zSendErrMsgF("ERROR:PHV_C2I:画像サイズの幅が不足"); return nil }
    if (rHeight < IMG_HEIGHT_MIN) { zSendErrMsgF("ERROR:PHV_C2I:画像サイズの高さが不足"); return nil }
    var iImage: UIImage? = nil
    let iSize: CGSize = CGSizeMake(rWidth, rHeight)
    let iOptions = PHImageRequestOptions()
    iOptions.resizeMode = PHImageRequestOptionsResizeMode.Fast // 縦横比を保ち、小さい辺が指定サイズで生成する
    iOptions.deliveryMode = PHImageRequestOptionsDeliveryMode.FastFormat   // .FastFormat, .Opportunistic (後で違いを調査)
    iOptions.synchronous = true
    //let iContentMode: PHImageContentMode = PHImageContentMode.AspectFill // 値メモ .AspectFill, .AspectFit
    PHImageManager.defaultManager().requestImageForAsset(rPHAsset, targetSize:iSize, 
        contentMode: .AspectFill, options:iOptions, resultHandler:{ (rImage, rInfo) -> Void in
        iImage = rImage
    })
    return iImage
}

最初に、サイズ指定の値を検査しています。検査に引っ掛かると、何もしないでnilを返します。

続いては、UIImage生成処理で指定するオプションの準備です。PHImageRequestOptionsインスタンスを生成し、必要な値に設定します。

オプション値を説明したSDK資料を見たのですが、詳しい動きが書いてありませんでした。仕方がないので、それらしい値に設定して、結果を表示させながら、上記ソースコードの値が残りました。この中では、deliveryModeに違いが感じられず、後で詳しく調べてみる必要があるでしょう。

オプション以外では、contentModeも指定する必要があります。縦横比を保持するAspectFillを選びました。また、これは前回紹介した工夫と同じように、コメント行で他の値を見つけやすくメモを残しています。

準備ができたらrequestImageForAssetメソッドを実行し、UIImageが生成できればそれを返し、生成できなければnilを返します。

このconvert2ImgFメソッドには、先頭にコメント行が何行も含まれています。実際に実行して気付いた点を、開発ツールのバージョンとともに、メモとして残しました。具体的には、元サイズより大きなサイズを指定しても、元サイズより大きくはならないとか、サイズ指定が最大値の指定ではなく、最小値の指定を意味しているようだとか、サイズ指定の値が小さいと、関係ないサイズで生成されるとかです。これらの点に関しては、仕上げの段階で再確認する必要があるでしょう。

このようにメモすれば、後で開発を再開した時に忘れることがなく、修正漏れが発生する心配がありません。開発時に気付いた点は、その時点で確実にメモすることが大事です。関係するソースコード上が、メモするのに最適な場所でしょう。

 

次は、生成する画像サイズの変更メソッドです。デフォルトの値を持っていますが、好きな値に変更したい場面が確実にあるでしょう。

// ======================================== 画像サイズの設定
func setImgSizeF(rWidth:CGFloat, _ rHeight:CGFloat) {
    if (rWidth < IMG_WIDTH_MIN) { zSendErrMsgF("ERROR:PHV_SIS:画像サイズの幅が不足"); return }
    if (rHeight < IMG_HEIGHT_MIN) { zSendErrMsgF("ERROR:PHV_SIS:画像サイズの高さが不足"); return }
    imgWidth = rWidth
    imgHeight = rHeight
}

処理内容としては、引数の値を検査して大丈夫なら、クラス変数に値を保存しているだけです。

 

続いては、画像を取得するメソッドです。いろいろな使い方を考慮し、3つも作ってみました。

// ======================================== 画像の取得
// 画像の取得
func getImageF() -> UIImage? {
    if isSelected {
        return imgView.image
    } else {
        return nil
    }
}
// 画像の取得(サイズ指定)
func getImageF(rWidth:CGFloat, _ rHeight:CGFloat) -> UIImage? {
    if (rWidth < IMG_WIDTH_MIN) { zSendErrMsgF("ERROR:PHV_GI:画像サイズの幅が不足"); return nil }
    if (rHeight < IMG_HEIGHT_MIN) { zSendErrMsgF("ERROR:PHV_GI:画像サイズの高さが不足"); return nil }
    if isSelected {
        let iImage: UIImage? = convert2ImgF(phAsset, rWidth, rHeight)
        return iImage
    } else {
        return nil
    }
}
// 画像参照の取得
func getAssetF() -> PHAsset? {
    if isSelected {
        return phAsset
    } else {
        return nil
    }
}

並んでる順に紹介します。

最初のメソッドは、ImageViewに表示しているUIImageを、そのまま返すタイプです。このUIImageは、このクラスのインスタンスに保持されているサイズ指定を使って、生成されたものになります。

次のメソッドは、引数にサイズ指定を含み、そのサイズに合わせて新たに生成したUIImageを返すタイプです。目的に合わせた大きさで画像を生成できます。

最後のメソッドは、UIImageではなく、PHAsset(画像参照情報)を返すタイプです。使うかどうかは不明ですが、とりあえず付けてみました。PHAssetからUIImageを生成するconvert2ImgFメソッドも外からアクセス可能にしているため、情報を保持しておけば、好きなときに好きなサイズでUIImageが生成できます。

 

いよいよ、最後のソースコードです。写真が選択されている状態か、アプリ側から判定できるメソッドです。

// ======================================== 選択中の判定
func isSelectedF() -> Bool {
    return isSelected
}

処理内容としては、内部のフラグを返しているだけです。でも、このメソッドは使い勝手に大きく関係しますから、クラスとしては必須です。この判定結果を知れることにより、「先に画像を選んでください」とか、適切なメッセージをアプリが出せるからです。

 

以上で、Swiftコードの紹介は終わりです。続いて、使用例を紹介しましょう。

このクラスの使い方は非常に簡単です。クラスのインスタンスを生成し、初期化してから好きなViewに貼り付けるだけで準備完了です。

// PhotoSelectorクラスを使う
let aPhotoSelector = PhotoSelector()
aPhotoSelector.setupF(10, 10, 900, 400)
viewBase.addSubview(aPhotoSelector)

たったこれだけで、フォトライブラリのアルバムから写真を選べるUIViewが画面上に追加できます。

同様に、選択した画像を取得するのも簡単です。たとえば「登録」ボタンに、以下のような関数を設定します。

// 「登録」ボタンに付ける処理
func addBtnF(rSender:UIButton) {
    if aPhotoSelector.isSelectedF() {
        if let iImg: UIImage = aPhotoSelector.getImageF(1000, 700) {
            //
            // ここに画像の登録処理を入れる
            //
            setMsgF(4, "選択中の画像を登録しました。")
        } else {
            setMsgF(4, "登録に失敗しました。")
        }
    } else {
        setMsgF(4, "先に画像を選んでください。")
    }
}

もし写真が選択中でなければ、選択を促すメッセージが表示されます。選択中であって、UIImageが無事に取得できれば登録処理へ進み、登録完了のメッセージが表示されます。逆にUIImageを取得できないときは、エラーメッセージだけを表示します。

 

以上のような使い方は、PhotoSelectorのUIViewを画面上に貼り付けています。画面上の1要素として、PhotoSelectorを使う形です。

それとは異なり、一時的に画面上に表示して、画像を選ばせるような使い方も可能です。たとえば、画面と同じサイズのUIView(画面を覆うためのUIViewです)を用意して、その中央にPhotoSelectorのUIViewを貼り付けます。また「登録」ボタンや「キャンセル」ボタンも、一緒に用意します。

それら全体を画面上に表示して覆い、画像を選択させます。PhotoSelectorを操作してから「登録」ボタンをタップすると、登録処理を済ませて、覆っていたUIViewごと消します。「キャンセル」ボタンをタップした場合は、何もせずに、覆っていたUIViewごと消します。このような形で作ると、画像選択のモーダルダイアログのようにも使えます。

 

実際に使ってみると、中央のTableViewが使いづらく感じました。意味不明な記号のような文字列(localIdentifierの値)が並ぶだけで、どんな画像なのか不明だからです。シミュレーターで試した表示は、次のようになりました。

使いやすくするためには、中央のTableViewを、文字列ではなく画像表示に変更すべきでしょう。画像表示なので、一般的なTableViewではなく、格子状の並びが適していそうです。その点だけ改良すれば、使い勝手も十分だと思います。アプリ開発が決定した段階で、変更を加える予定です。

ここまでの状態でも、肝となるPhotosフレームワークに関係する部分は、ほぼ作り終わっています。細かな設定を確認する必要はありますが、アプリ開発が決まった時点でも構わないと思います。この段階まで作ってあれば、残りは細かな部分だけなので、短時間でなんとでもなるでしょう。というわけで、試作は一旦終了としました。

 

今回は、独自ライブラリを試作するという形で、完成まで達していない例を紹介してみました。使ったことのない機能の場合、近いうちに使いそうだと感じた段階で、とりあえずの試作を作ってみるのはアリだと思います。十分なテストはしていなくても、一般的な使い方でなら、基本的な機能が動く程度まで作っておけば、準備段階としては十分と言えます。

もちろん試作であっても、いろいろなアプリで使える形に作るのは必須です。最初は簡単な機能で作り、アプリ側とのインターフェースをできるだけ変えずに、機能を少しずつ加えていくのが、上手な作り方だと思います。

 

(使用開発ツール:Xcode 6.3.1, SDK iOS 8.3)

0 件のコメント:

コメントを投稿