「Start Developing iOS Apps (Swift)」でFoodTracker Appを実際に作ってみる その4[View Controllersを加工する]

Start Developing iOS AppでFoodTrackerを実際に作ってみる、その4

公式にある「Start Developing iOS Apps (Swift)」を使って実際にFoodTracker Appを作ってみる、というシリーズの第四回目です。前回はこちら

「Start Developing iOS Apps (Swift)」でFoodTracker Appを実際に作ってみる その3 [UIとコードを繋ぐ]
公式にある「Start Developing iOS Apps (Swift)」を使って実際にFoodTracker Appを作ってみる、と...

前回は作ったUIとソースコードを繋ぐ方法を学びました。今回はView Controllerをさらに加工していきます。

この記事を書いている時点でのXcodeのバージョンは7.3です。

追記: Swift 3がリリースされたので、Swift 3/Xcode 8.0の仕様に合わせました。バージョンによる違い、警告やエラー等に関してはXcode 7.3の記述をそのまま載せる場合がありますが、その際は補足します。また、一部の画像は以前のバージョンのXcodeやシミュレータのモノを流用している場合があります。

目次

View Controllerを理解する

今回の学習目的(Learning Objectives)は、

  • View controllerのライフサイクルを理解し、callback(viewDidLoadviewWillAppearviewDidAppear)がいつ起こるかを知る
  • View controller間でデータのやりとりをする
  • View controllerを閉じる(dismiss)
  • ジェスチャー識別機能(gesture recognizer)を使う
  • UIView/UIControlクラス階層からオブジェクトの振る舞いを予想する
  • Asset catalogを使って、画像素材をプロジェクトに追加する

です。英語を日本語に変換出来ないのがもどかしいですが、和訳しても(life cycle ↔ ライフサイクル。寿命?)理解度が深まるような気がしないので、うまいこと変換出来ない言葉はカタカナもしくは英語のまま使います。

Part4が終わると、料理の写真をUIに表示出来るようになります。なんだかアプリらしくなってきましたね。
Xcode: 写真をUIに表示

View Controllerのライフサイクルを理解する | Understand the View Controller Lifecycle

View controllerの持つメソッド、呼び出されるタイミング

ここまでは、

してきました。現時点では、FoodTrackerアプリのsceneは1つで、そのUIは1つのview controller(ViewController)で管理しています。

もっと複雑なアプリを作成するようになると、よりたくさんのsceneを扱うようになります。その場合、view controllerの仕組みを良く理解しておくのが重要で、例えば「どの状態遷移時にどのメソッドが呼ばれるのか?」ということが分かると、どのメソッドをカスタマイズすれば良いかということが分かり、view controllerを適切に管理出来るようになるはずです。

[図解]ViewControllerのライフサイクル
公式に非常に分かりやすい図があったので、同じものを作ってみました。これを見ると、viewsが表示されたり非表示の状態になる際、どのメソッドが呼び出されるのかが一目で分かります。UIViewControllerのサブクラスでは、これらのメソッドを継承して(overrideして)カスタマイズすることで、適切なタイミングで特定の動作を実行することが可能になります。

UIViewControllerのメソッドが呼び出されるタイミングは、次のようになっています。

viewDidLoad()

View controllerの「content view」(view階層の一番上のview)が作られた時に呼び出され、ストーリーボードから読み込まれます。このメソッドは初期セットアップが目的のようです。

However, because views may be purged due to limited resources in an app, there is no guarantee that it will be called only once.

メモリの制約上の理由か分かりませんが、初期セットアップ自体は何度か実行される可能性があるようです。

「Purge」のニュアンスが今いち分かりませんが、「追放する」「消去する」「除去する」という意味です。全然関係ないですが、FF13用語の「パージ」はここから来ているはずです。

viewWillAppear()

Viewが表示される前に実行したい操作(演算等)を置くメソッドとして用意されたものです。Viewが表示されるかどうかは、他のviewsに依存する可能性があるので、常にcontent viewがスクリーン上に表示される直前に呼び出されるようです。

viewDidAppear()

Viewが表示された直後に実行したい操作を置くメソッドで、例えばデータを取り出したり、アニメーションを動かしたりするような操作を置いたりします。viewWillAppear()が直前だったのに対して、viewDiddAppear()は常にcontent viewが表示される直後に呼び出されるようです。

メソッド名のwillやdidの意味

箇条書きにしてまとめると、

  • viewWillAppear()は、content viewが表示される直前
  • viewDidAppear()は、content viewが表示された直後

というタイミングで呼び出されます。メソッドの名前と呼び出されるタイミングを見ると、will(未来)やdid(過去)はメソッドを現在とした時、content viewが表示されるのはいつか?という視点です。これはプロパティ監視(property observers)を思い出すと分かりやすいかもしれません。

Model-View-Controller(MVC)パターン

現時点でのViewControllerを見ると分かるように、すでにviewDidLoad()メソッドを実装しています。

override func viewDidLoad() {
    super.viewDidLoad()
    
    // Handle the text field’s user input through delegate callbacks.
    nameTextField.delegate = self
}

View controllersがviewsとdata model間コミュニケーションの中継装置を担うようなアプリデザインのスタイルを、MVC(Model-View-Controller)と呼ぶそうです。Data modelは独自型、つまり自作クラスだと思えば良いかと思います。MVCパターンでは、それぞれ

Modelがアプリのデータ管理、viewsがUIを表示、controllersがviewsを管理

という役割をこなしています。ユーザからの何らかのアクションに対応して、modelからデータを持ってきてviewsに表示する場合、controllersがmodelとviewsがやり取りするための中継地点になります。

FoodTrackerのアプリはMVCの原則に従って作られています。というか、view controllerを使ってアプリを作ると、MVCに従ったアプリになるのではないかと思います。MVCのことを頭の片隅に覚えておいて、今作っているsceneの最終レイアウトを作ります。最終レイアウトでは、画面の下側に料理の写真が表示されるようなUIを追加します。

料理の写真を追加する | Add a Meal Photo

今回追加するUIは、写真を表示するためのUIです。何の写真かと言うと、当然料理の写真です。この機能を実装するために使うのがUIImageViewというクラスで、写真を表示するためのUI要素になります。新しいUIなので、少し丁寧に説明します。

ストーリーボード(Main.storyboard)を開く

ViewController.swiftを開いている場合、ストーリーボードに切り替えます。

Object Libraryを開く

Utility area(Xcodeの右側にあるメニュー)の一番下にある「Object Library」を開きます。Object Libraryは並んでいる4つのボタンの、左から3番目です。メニューから開く場合は

View > Utilities > Show Object Library (キーボードショートカットcontrol+option+⌘+3)

Filter fieldで「image」と入力してimage viewを探す

Object Libraryの下側にある検索窓「filter field」で「image」と入力すると、

Xcode: Object Libraryで「image」を検索
このように「Image View」が出て来ます。

Image Viewをsceneにドラッグ&ドロップ

Image Viewをsceneにドラッグして、stack view内部でボタンの下に落とします。

Xcode: sceneにimage viewを追加
そうすると、上の画像のようになるはずです。

Image Viewを選択した状態でsize inspectorを開く

Image Viewをドロップした時点で選択されていると思いますが、されてない場合は落としたimage viewをクリックして選択。その状態で、utility areaの「size inspector」を開きます。Size inspectorは上側にあるボタンの左から5つ目(右から2つ目)のボタンです。

Xcode: Image viewのsize inspector
Size inspectorを開いた状態が上の画像です。

Placeholder指定 & Image Viewの大きさ指定

Size inspectorの下の方にある「Intrinsic Size」で「Placeholder」を選択。Intrinsic Sizeメニューが見当たらない場合は、size inspector内で下にスクロールすると表示されます。もしくはXcode自体を少し拡大します。

Placeholderを選択すると、サイズを指定する「Width(幅)」「Height(高さ)」が表示されるので、その値を両方320にセットします。

Xcode: Image viewをplaceholderにセット、サイズも変更
WidthとHeightを設定した後の状態が上の画像。見た目の大きさを320×320にセットしたので、scene上でのimage viewがこのように大きくなるはずです。

Pinメニューを開き、縦横比(aspect ratio)を固定

Pinメニューを開きます。Pinメニューはcanvas(sceneが表示されている場所)の右下にある4つ並んでいるボタンの右から2つ目です。クリックして「Aspect Ratio」という項目のラジオボタンにチェックを入れます。

Aspect ratioというのは図形の縦横比のことです。

ここにチェックを入れると今セットしている縦と横の長さの比が固定されるので、image viewの大きさは320:320=1:1で固定されます。

Xcode: Image viewのaspect ratioを固定
Aspect Ratioにチェックを入れた状態が上の画像。最後に「Add 1 constraint」ボタンを押して設定完了です。

Attribute Inspectorを開いて、「User Interaction Enabled」項目にチェックを入れる

次に、Utility areaから「Attribute Inspector」を開きます。Attribute Inspectorは、メニュー選択ボタンの左から4番目(右から3番目)です。

Attribute Inspectorを開いたら、「interaction」という項目を探して、そこにある「User Interaction Enabled」という項目横のラジオボタンにチェックを入れます。

Xcode: Image viewのattribute inspectorでinteraction設定
設定完了後の状態が上の画像です。ここにチェックを入れないと、幾らUIとソースコードを繋いでも何も起こりません。

デフォルト画像を表示する | Display a Default Photo

写真を追加出来るスペースは確保しました。これは開発者側からは自明ですが、ユーザにとっては「何だ?この空きスペースは?」ということになりかねません。UIの役割がぱっと見て分かるように、「ここに写真を追加することが出来ます」とユーザに示す画像を置きます。

Xcode: Image Viewに追加するデフォルトの画像
置くのは上に示した画像です。公式だと「No Photo Selected」と書かれた画像でしたが、「タップして写真を選択/撮影」というユーザ操作を直接示した言葉に変更してみました。公式ページで使われている画像や写真自体は

Start Developing iOS Apps (Swift) – Work with View Controllers
の一番下にあるXcode fileへのリンクに含まれていますので、必要ならダウンロードして素材を使うと良いかと思います。

プロジェクトに画像を追加する

Project navigatorでAssets.xcassetsを選択

Xcode: Asset.xcassetsファイルを選択
タイトル通りですが、project navigatorで「Assets.xcassets」というファイルを選択します(上の画像参照)。Xcodeで「asset catalog」というのは、画像などをUIの一部として保存しておくための場所のようです。

左下+ボタンをクリック、「New Image Set」を選択

Xcode: Asset.xcassetsでNew Image Setを追加
Asset.xcassetsをクリックすると右側にアウトラインビューのような枠が出て来ますが、その一番下にある+ボタン(赤丸で囲まれた部分)をクリックして、「New Image Set」を選択します(上の画像参照)。

「New Image Set」の名前を「defaultPhoto」に書き換え

Xcode: New Image Setの名前をdefaultPhotoに変更
そうすると「AppIcon」の下に「New Image Set」が出来ます。これをダブルクリックすると名前を変更出来るようになるので、名前を「defaultPhoto」に変更(上の画像参照)。

画像をドラッグ&ドロップ

画像を追加出来る場所を確保したので、次は実際に画像をXcode上に保存します。

Xcode: New Image Setに新しい画像をドラッグ
追加したい画像をドラッグして、「2×」と書いてある場所にドロップします(上の画像参照)。

2x is the display resolution for the iPhone 6 Simulator that you’re using in these lessons, so the image will look best at this resolution.

とあるように、「2×」という部分はiPhone 6シミュレータ用の解像度のようです。

これでasset catalogに画像が追加されたので、次はこの画像をimage viewに表示させます。

Image viewにデフォルト画像を表示する

Xcode: Image Viewに画像を指定

  1. ストーリーボードを開きます
  2. 「Image View」をクリックして選択
  3. Image Viewを選択した状態で、attribute inspectorを(utility areaで)開きます
  4. Attribute inspectorメニューで、「Image」という項目を探して「defaultPhoto」を選択(上の画像参照)
もしattribute inspectorの「Image」で「defaultPhoto」が出てこない場合、一度別のinspectorを選択してattribute inspectorを選択し直すと表示されるかもしれません。

シミュレータで動作確認

ここで設定したデフォルト画像の表示を実機で確認してみます。画像はiPhone6用に最適化されているのかもしれませんが、iPhone 6s Plusのテストでも問題なく表示されました。

Xcode: Simulatorでimage viewにセットした画像を確認
画像を自作した場合で、画像が小さく表示されていたり、または左右どちらかに寄っている場合は、画像の解像度が足りないのかもしれません。私も最初は300×300くらいで作っていましたが、ものすごく小さく表示されたり、中心に表示されませんでした。ちなみに、上で表示している画像の大きさは1920×1920です。他にもうまいやり方があるのかもしれませんが、思いつかなかったので解像度を上げてゴリ押ししました。

Image Viewとコードを繋ぐ | Connect the Image View to Code

Image ViewをアウトレットとしてViewController.swiftに追加

Image Viewにはデフォルトで表示される画像を設置しました。次はこの画像を動的に(アプリ実行時に)変更する機能を実装します。コード内から画像(image view)にアクセスするには、アウトレット(outlets)を用意しないといけません。したがって、先ずimage viewとViewController.swiftのコードを繋ぎます。

基本的な手順はラベルやテキストフィールドのアウトレットを作った場合と同じですので箇条書きにします。

  • Assistant editorを開く
  • (必要なら)スペース確保のためproject navigator、utility areaやoutline viewを隠す
  • ストーリーボードでimage viewをクリックして選択
  • controlを押しながらViewController.swiftまでドラッグ、これまで追加したアウトレットの下でドロップ
  • アウトレットの名前はphotoImageView
  • 「Connect」をクリック

Xcode: Image viewをアウトレットとしてViewController.swiftに追加
上の画像はimage viewをアウトレットとして追加する直前です。

Connectをクリックすると、

@IBOutlet weak var photoImageView: UIImageView!

がViewController.swiftに追加されます。

ビュー(views)とコントロール(controls)

この時点でコードからimage viewへアクセスすることが可能です。しかし、image viewにはこのままではアクション(action method)を設定することが出来ません。試しにimage viewをcontrol+ドラッグすると分かりますが、アクションの選択肢が出て来ません。

「ボタンの場合は一発でアクション(action method)を設定出来たのになんで?」と思うかもしれませんが、ボタンは所謂「controls」に分類されるオブジェクトで、image viewは名前の通り「views」に分類されるオブジェクトです。Viewsに(現時点で)アクションが設定出来ないのは、この違いから来ています。

コントロール(controls)というのはviewsの特殊形態になっていて、viewsはコンテンツを表示する部品であるのに対して、controlsはコンテンツを加工(変更)する部品です。実際コントロールはUIControlというクラスで、これはUIViewのサブクラスになっているようです。

Image viewをcontrolsのように取り扱うため、次のセクションでは「Gesture Recognizer」というオブジェクトを作ってみます。

Gesture Recognizerを作る | Create a Gesture Recognizer

Gesture Recognizerでviewにcontrolの機能を付ける

Image viewはviewなので、ボタン等のcontrolのように、入力に対して反応するようにはデザインされていません。これはimage viewにアクションが作れないことからも分かると思います。そこで出番となるのが「Gesture Recognizer」です。

「Gesture」は「ジェスチャー」で、これは日本語としてそのままでも使いますし、和訳すると「身振り」「手振り」という意味です。また、「Recognizer」というのは「recognize」という動詞から派生している名詞で、recognizeは「分かる」「認識する」「認める」等の意味です。

Gesture recognizerというオブジェクトを使うと、viewにcontrolの機能を追加することが出来るようです。ここでジェスチャーと呼んでいるのは、例えば「スワイプ」、「ピンチ」、「タップ」、「回転」などなど、ユーザが実際にデバイスを触っている時に実行する動作のことです。

Image viewにタップ用のgesture recognizerを追加する

これからimage viewにくっ付けるgesture recognizerはタップ用(UITapGestureRecognizer)で、実装したいのは

ユーザがimage viewをタップすると、それをジェスチャー(gesture)として認識してアクション(action method)を実行する

という機能です。

Object Libraryで「tap gesture」を検索

Xcode: Object Libraryでtap gesture recognizerを探す
Utility areaの下側にあるObject Libraryで、「tap gesture(tapだけでも行けます)」と入力して「Tap Gesture Recognizer」を探します。

Tap Gesture RecognizerをImage Viewにドラッグ

Xcode: Tap Gesture RecognizerをImage Viewにドラッグ
Object Libraryで見つけた「Tap Gesture Recognizer」をimage viewにドラッグ&ドロップします。そうすると、

Xcode: Scene dockに出てくるtap gesture recognizer
上の画像から分かるように、sceneの上にあるツールバーのような場所(scene dock)に、tap gesture recognizerのアイコンが出て来ます。

Gesture Recognizerとコードを繋ぐ | Connect the Gesture Recognizer to Code

次は作ったタップ用gesture recognizerをViewController.swiftのアクション(action method)と繋ぎます。手順はアクションを作る場合とほぼ一緒です。

  • controlボタンを押しながらgesture recognizerをViewController.swiftにドラッグ
  • // MARK: Actionsコメントの下にドロップ
  • 出て来たダイアログの一番上「Connection」は「Action」にセット
  • 名前(Name)はselectImageFromPhotoLibrary
  • 型(Type)はUITapGestureRecognizer

Xcode: tap gesture recognizerをViewController.swiftにcontrl+ドラッグしている瞬間
Tap gesture recognizerをドラッグして落とす直前の状態が上の画像。アウトレットやアクションを設定するのと同じです。

Xcode: tap gesture recognizerをactionとして設定
箇条書きにした上記の設定が終わった状態が上の画像。後はダイアログ右下の「connect」をクリックして完了です。

そうすると、

@IBAction func selectImageFromPhotoLibrary(_ sender: UITapGestureRecognizer) {
}

というアクションがViewController.swiftに出来ます。

タップに反応するImage Pickerを作る | Create an Image Picker to Respond to User Taps

UIImagePickerControllerクラスで写真を使う

上記のメソッド(またはaction or action method)に付けた名前から機能が想像出来るかもしれませんが、メソッドの具体的な中身は

ユーザがタップしたら写真を「photo library」から選択できるメソッド

という感じになります。

この機能を実現するクラスがUIImagePickerControllerというクラスです。クラスの説明を見ると、

The UIImagePickerController class manages customizable, system-supplied user interfaces for taking pictures and movies on supported devices, and for choosing saved images and movies for use in your app. An image picker controller manages user interactions and delivers the results of those interactions to a delegate object.

抜粋: UIImagePickerController Class Referenceより

とあるように、UIImagePickerControllerには、写真(または動画)を撮ったり、既に保存されたメディアを使うための機能が備わっていることが分かります。

テキストフィールドにユーザ入力を処理する機能を実装した場合と全く同様ですが、今回実装したい機能もdelegation(委譲)デザインパターンを使います。つまり、UIImagePickerControllerのdelegate(UIImagePickerControllerDelegate)というプロトコルをViewControllerに採用します。

UIImagePickerControllerDelegateとUINavigationControllerDelegateをプロトコルとして採用

手順はテキストフィールドの場合と全く同じですが、詳しく説明します。

Assistant editorを閉じて、standard editor表示に戻す

先程UIとコードを繋ぐ操作を行っていましたので、Xcodeではassistant editorが開いている状態のはずです。ViewController.swiftソースコードを編集するので、assistant editorを閉じてstandard editorを開きます。また、navigator areaとutility areaも隠れている状態であれば、それらも開きます。

UITextFieldDelegateの後ろにコンマ(,)を入れて、UIImagePickerControllerDelegateを採用

現在のクラス宣言が、

class ViewController: UIViewController, UITextFieldDelegate {

このようになっているはずですが、UIImagePickerControllerDelegateを新たに採用して、

class ViewController: UIViewController, UITextFieldDelegate,
    UIImagePickerControllerDelegate {

上記のように書き換えます。クラス継承と違い、プロトコルは複数採用することが可能です。複数のプロトコルを採用する場合、コンマ(,)でプロトコルを分けます。

UINavigationControllerDelegateプロトコルも採用

同様にUINavigationControllerDelegateプロトコルも採用します。最終的には、

class ViewController: UIViewController, UITextFieldDelegate,
    UIImagePickerControllerDelegate, UINavigationControllerDelegate {

このようになっているはずです。

UINavigationControllerってなんじゃい?と思いますが、クラスリファレンスを見ると、階層構造を持ったコンテンツの管理(例えばメニューをタップした場合、一段下層のコンテンツに遷移する)をするクラスのようです。公式に載っている例の画像が非常に分かりやすいです。
参考:UINavigationController Class Reference

なぜ2つのプロトコルを採用する必要があるのか?

これはUIImagePickerControllerのクラスリファレンスを見て分かりましたが、UIImagePickerControllerが持っているdelegateが、

weak var delegate: (UIImagePickerControllerDelegate & UINavigationControllerDelegate)? { get set }

という具合に、2つのプロトコルを要求しているからです。この書き方は馴染みが余りないかもしれませんが、2つ以上の複数のプロトコルを要求するようなケースを、protocol compositionと呼び、

A & B & C ...

という書き方をします(A, B, Cはプロトコル)。

試しにやってみると分かりますが、UIImagePickerControllerDelegateだけを採用した場合、後でUIImagePickerControllerdelegateViewControllerを代入する時に怒られます。

// 出てくるエラー
//Cannot assign value of type 'ViewController' to type '(UIImagePickerControllerDelegate & UINavigationControllerDelegate)?'

selectImageFromPhotoLibrary(_:)アクションメソッドを実装する

2つのプロトコルを採用したら、次はselectImageFromPhotoLibrary(_:)の中身を実装していきます。

キーボードを隠す

先ず、下記のコードを実装します。これは前回テキストフィールドにアクションを追加する際に出て来た、「キーボードを隠す」という機能の実装になります。

// Hide the keyboard
nameTextField.resignFirstResponder()

なんでこの機能が必要なのか?と思うかもしれません。これは、

ユーザがキーボードを開いて何かしらの文字列を入力している最中に画像をタップした

というケースを想定したものです。キーボードが開きっぱなしだと色々困るので、「(画像をタップすると)キーボードが隠れる」という機能を追加しています。

写真をライブラリから選択する

次に、「写真をライブラリから選択する」という機能を実装します。先にコードを書くと、

// UIImagePickerController is a view controller that lets a user pick media from their photo library.
let imagePickerController = UIImagePickerController()
        
// Only allow photos to be picked, not taken
imagePickerController.sourceType = .photoLibrary

上記のようになります。これを、先程追加したnameTextField.resignFirstResponder()以下に追加します。

ここでは「写真をどこから持ってくるか?」ということを決めていて、2番目に追加した

imagePickerController.sourceType = .photoLibrary

このソースコードは、「写真をphoto libraryから持ってきます」と言ってます。.photoLibraryという書式は、(この後説明しますが)sourceTypeの型が列挙型として自明なので使える省略書式です。もし「分かりにくいからちゃんと書きたい」と思ったら、

imagePickerController.sourceType = UIImagePickerControllerSourceType.photoLibrary

と書くことも可能です。

写真のソースは3種類: UIImagePickerControllerSourceType列挙型

もう少し詳しく説明すると、imagePickerControllerUIImagePickerControllerのインスタンス)は、変数sourceTypeを持っていて、その型はUIImagePickerControllerSourceTypeです。UIImagePickerControllerSourceTypeというのは列挙型で、UIImagePickerControllerの中で定義されています。

enum UIImagePickerControllerSourceType: Int {
    case photoLibrary = 0
    case camera = 1
    case savedPhotosAlbum = 2
}

Raw valueは整数型(Int)で、名前から察するに写真のソースとしては3つ、「フォトライブラリ」「カメラ」「アルバムに保存された写真」、があるのが分かります。

参考: UIImagePickerControllerSourceType, SwiftのAPI Reference

View controllerをimagePickerControllerのdelegateに代入

// Make sure ViewController is notified when the user picks an image.
imagePickerController.delegate = self

これはタイトルの通りで、テキストフィールドの場合と同様に、imagePickerControllerが持っているdelegateという弱参照に自分自身(self)を代入します。今ViewControllerクラスを編集しているので、ここでの自分自身selfViewControllerを指しています。

メソッドpresent(_:animated:completion:)を追加

次に追加するのは以下のメソッドです。

present(imagePickerController, animated: true, completion: nil)

メソッドのみの記述ですが、

self.present(imagePickerController, animated: true, completion: nil)

という具合にselfViewController)を付けると、ViewControllerが呼び出しているメソッドであることが、はっきり分かると思います。

このメソッドは最初のパラメータとして取ったview controllerを最前面に表示するメソッドです。今パラメータとしてはimagePickerControllerが入っていますので、画像をタップした結果としてimagePickerControllerを表示する、という機能を実装したことになります。

2番目のパラメータanimatedは、imagePickerControllerを表示する時にアニメーションを使用するかどうかというパラメータです。

実際にやってみると分かりますが、ON(true)にすると画面遷移が少しゆっくりになり、OFF(false)にすると一瞬で画面が切り替わります。

3番目のパラメータcompletionは、型(() -> Void)?を取るクロージャです。「completion handler」と呼ばれているようで、メソッドが終了する際に呼び出されるので「completion」(完了する、終了するという意味の名詞)という名前が付いています。型を見ると分かりますがオプショナル型です。今はこのパラメータが必要ないようなのでnilを代入してあります。

最終的なselectImageFromPhotoLibrary(_:)の中身

全部実装した後のメソッドの中身は、

@IBAction func selectImageFromPhotoLibrary(_ sender: UITapGestureRecognizer) {
    // Hide the keyboard
    nameTextField.resignFirstResponder()

    // UIImagePickerController is a view controller that lets a user pick media from their photo library
    let imagePickerController = UIImagePickerController()

    // Only allow photos to be picked, not taken
    imagePickerController.sourceType = .photoLibrary

    // Make sure ViewController is notified when the user picks an image
    imagePickerController.delegate = self

    present(imagePickerController, animated: true, completion: nil)
}

このようになります。このメソッドの中で、imagePickerControllerUIImagePickerControllerのインスタンス)がViewControllerに機能を委譲しています。

ユーザが写真を選択出来るようにするには、UIImagePickerControllerDelegateが持っているメソッド2つを実装する必要があります。

func imagePickerControllerDidCancel(picker: UIImagePickerController)
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject])

次はこれら2つのメソッドを実際に実装する手順を詳しく説明します。

imagePickerControllerDidCancel(_:)メソッドを実装する

最初のメソッドimagePickerControllerDidCancel(_:)が呼び出されるタイミングは、ユーザがimage pickerで表示されている「Cancel」ボタンを押した時のようです。メソッドの名前からもなんとなく推測できますが、このメソッドはUIImagePickerControllerを閉じる(表示を辞める)メソッドです。

Xcode: Image pickerのcancelボタン
シミュレータを起動して「タップして写真を選択/撮影」画像をクリックすると、上の画像のような画面に遷移しますが、右上の赤い丸で囲んでいる「Cancel」ボタンのことを指しています。

// MARK: UIImagePickerControllerDelegateを追加

先ずお馴染みの// MARK:コメントを追加します。どこでも良いと思いますが、// MARK: Actionsのすぐ上に追加とあるので、そこに

// MARK: UIImagePickerControllerDelegate

を追加します。このコメントを入れる理由は2つあります。1つはソースコードを見た時に分かり易いということで、もう1つはfunction menuを開いた時に項目として表示させることが出来るという点です。

imagePickerControllerDidCancel(_:)を実装

先程追加した// MARK:コメント直下にメソッドを実装します。Xcodeの補完機能を使えば簡単に実装出来ると思います。

// MARK: UIImagePickerControllerDelegate
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
    // Dismiss the picker if the user canceled
    dismiss(animated: true, completion: nil)
}

中に入っているdismiss(animated:completion:)というメソッドがimage picker(今表示されているview controller)の表示を辞めるメソッドです。パラメータは先程出て来たpresent(_:animated:completion)の、2番目と3番目のパラメータと同じです。

imagePickerController(_:didFinishPickingMediaWithInfo:)メソッドを実装する

2つ目に実装するメソッドが、ユーザが写真を選択した時に呼び出されるメインのメソッドになっています。このメソッドを利用することで、画像やら(ユーザが選択した)写真等々に対して何かしらの操作を行うことが可能です。今回実装するのは、

ユーザが選択した写真をUIに表示する

という機能です。

先に最終形を載せておくと、

func imagePickerController(_ picker: UIImagePickerController,
  didFinishPickingMediaWithInfo info: [String : Any]) {
    // The info dictionary contains multiple representations of the image, and this uses the original.
    let selectedImage = info[UIImagePickerControllerOriginalImage] as! UIImage

    // Select photoImageView to display the selected image
    photoImageView.image = selectedImage

    // Dismiss the picker.
    dismiss(animated: true, completion: nil)
}

このようなメソッドを追加することになります。以下、一行ずつ順番に見ていきます。

メソッドの外枠をXcodeの補完機能で追加

メソッドの外枠だけを抜き出すと、

func imagePickerController(_ picker: UIImagePickerController, 
  didFinishPickingMediaWithInfo info: [String : Any]) {
}

となります。非常に複雑に見えますが、Xcodeの補完機能を使うと、このようなメソッドでも簡単に追加出来ます。1つ目のパラメータはimage picker、2つ目のパラメータは次に出て来ますが辞書型配列(dictionary)です(一般的には連想配列という、インデックスに整数以外の型が取れる配列)。この辞書型配列infoは、keyがString、valueがAnyになっています。

タップされた写真(画像)を辞書型配列から取り出す

メソッドの中身で、最初に記述されているのが以下の行です。

// The info dictionary contains multiple representations of the image, and this uses the original.
let selectedImage = info[UIImagePickerControllerOriginalImage] as! UIImage

これは、プログラム的には

辞書型配列の要素をUIImage型として取り出して、それでselectedImageを初期化する

ということです。

infoは様々な形でメディア(ここでは写真)を保存しているようで、その種類は文字列定数としてUIImagePickerControllerDelegateプロトコル内で定義されています。今辞書へのkeyとしてUIImagePickerControllerOriginalImageが使われていますが、名前から分かるように、これは「オリジナルの写真を取り出しなさい」というString型の定数です。また、infoの要素はAnyなので、UIImage型キャスト(as!しています。

取り出した写真をUIに追加する

次のコードでは、

// Set photoImageView to display the selected image.
photoImageView.image = selectedImage

このように、ちょっと前に作ったアウトレットが持っているimageプロパティに、先程取り出した写真を代入します。これでUIに写真が表示されるはずです。

Image pickerを閉じる

// Dismiss the picker.
dismiss(animated: true, completion: nil)

最初のメソッドでも実装したものですが、最後にimage pickerを閉じるためのメソッドを追加しておきます。

フォトライブラリのプライバシー設定

では、ここまでで実装した機能を実際にシミュレータで確認してみます。

クラッシュ!

Swift 2.xまではこれでうまくいっていましたが、Swift 3/Xcode 8系の場合はここでクラッシュするはずです。編集領域の画面には良く分からないエラーメッセージが出ると思いますが、注目するのはdebug areaに表示されるエラーです。

Xcode: フォトライブラリのプライバシー設定がInfo.plistに載っていないというエラー
上記画像に示したようなエラーがdebug area(editor area下に表示される領域)に表示されるはずです。Debug areaが開いていない場合は、toolbarの右上のボタンを押します。エラーが表示されるのはdebug area右側の領域なので、そこが開いていない場合は、debug area右下にあるボタンを押して開きます。

表示されているエラーは

This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app’s Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data.

です。重要な部分にマーカーを付けましたが、どうやらInfo.plistというファイルに記述が必要のようです。フォトライブラリのような個人情報にアクセスする際にはユーザの同意が必要という仕様になったようです(iOS10から)。

Navigator areaからInfo.plistを開く

Info.plistファイルはViewController.swift等が置かれているnavigator areaメニューに表示されているはずです。クリックして開くと、

Xcode: Info.plistに新しい行を加える
こんな感じになっていると思います。

Info.plistに新しい項目(行)を追加する

今必要なのはフォトライブラリにアクセスするための記述ですが、新しい項目を追加するには2つ方法があります(上の画像参照)。

  • 既存の行(どこでも可)にカーソルを合わせた時に表示される「+マーク」をクリック
  • 画面上でcontrol+クリック、表示されるメニューから「Add Row」を選択

「Privacy – Photo Library Usage Description」項目を見つけて選択

Xcode8: フォトライブラリのプライバシー設定項目を選択
追加すると上の画像のように、名前を選択するプルダウンメニューが出るので、

Privacy – Photo Library Usage Description

という項目を探して選択します。

必要な記述を入力

エラーメッセージにも表示されていましたが、「フォトライブラリをどう使うか?」をユーザに説明する記述も必要です。先程追加した項目の右側「Value」の部分が空白になっているので、そこをクリック

Xcode8: 追加項目の記述(どう使うのか?)
どう使うのかを端的に書いておきます。今回は英語で書きましたが、日本向けアプリだと日本語で書くべきかもしれません。

シミュレータで動作確認

フォトライブラリのプライバシー設定後、再度シミュレータで動作確認してみます。今度はうまく行くはずです。

写真を選択

iOS Simulator: フォトライブラリへのアクセス許可
「タップして写真を選択/撮影」をクリックすると、アプリが写真を使うことを許可するかどうか聞いてくるので許可します。この時表示される文章が、先程設定した記述と一致しているはずです。上の画像だと英語ですが、右側の「OK」を選択。

Xcode: Image pickerをシミュレータでテスト
そうすると、上記画像のような画面になります。

右上にある「Cancel」ボタンを押すと、前の画面に戻ります。ここがimagePickerControllerDidCancel(_:)で実装した部分です。「Photos」の「Moments」と「Camera Roll」をクリックすると、一階層下のメニューに移動して、写真を選べる画面になります。

新しい写真を追加

最初から入っている写真を見ると分かりますが、景色の写真ばかりで料理の写真はありません。料理の写真を追加するには、「タップして写真を選択/撮影」に追加したい写真をドラッグ&ドロップします。自前の料理の写真を追加しても良いですし、公式に置いてあるサンプルコードの中にある料理の画像を使っても良いです。

Xcode: 料理の写真をシミュレータに追加
写真をドラッグ&ドロップして追加すると、上記画像のように、新しい写真が追加されているのが分かります。今追加した写真は、公式のサンプルコードから持ってきた料理の写真です。

写真をUIに表示

UIに写真を表示したい場合は、カメラロールから好きな写真を選択するだけです。

Xcode: 写真をUIに表示

まとめ

これで写真を選択して(または新たに追加して)、UIにそれを表示させることが出来るような機能を追加出来ました。

次の回では、料理の評価(rating)が出来る機能をUIに追加します。

「Start Developing iOS Apps (Swift)」でFoodTracker Appを実際に作ってみる その5[カスタムコントロールを実装する]
公式にある「Start Developing iOS Apps (Swift)」を使って実際にFoodTracker Appを作ってみる、と...
スポンサーリンク
広告1
広告1

シェアする

フォローする