「Start Developing iOS Apps (Swift)」でFoodTracker Appを実際に作ってみる その7[テーブルビューを作る]

「Start Developing iOS Apps (Swift)」でFoodTracker Appを実際に作ってみる その7[テーブルビューを作る]

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

「Start Developing iOS Apps (Swift)」でFoodTracker Appを実際に作ってみる その6[データモデルを定義する]
公式にある「Start Developing iOS Apps (Swift)」を使って実際にFoodTracker Appを作ってみる、と...

前回はデータモデル(data model)として、FoodTrackerに必要な情報を管理出来るMealクラスを作り、ユニットテストを実行してクラスがちゃんと動くことを確認しました。今回はいよいよFoodTrackerアプリのメイン(起動)画面を作ります。新たにsceneを追加して、料理をリスト表示出来るテーブルビュー(table view-based scene)を作っていきます。

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

目次

テーブルビューを作る

いつもの学習目的(Learning Objectives)です。この回が終わったら、

  • 2つ目のsceneをストーリーボード上で作る
  • テーブルビューの重要な構成要素を理解する
  • 自作のテーブルビューセルを作るまたはデザインする
  • テーブルビュー用data sourceとdelegateの役割を理解する
  • 配列を使ってデータを格納し、それを使用する
  • テーブルビューで動的データを表示する

ことが出来るようになるのが目標。この回が終わった時点で、テーブルビュー表示のUIがアプリの起動画面になり、各料理が

Xcode: 料理のリスト表示のサンプル
こんな感じでリスト表示されます。

起動画面を作る | Create the Opening Scene

ここまでのFoodTrackerアプリは、1つのsceneのみをview controllerで管理してきました。このview controllerは

料理の写真やその評価をユーザが追加して、それを画面に表示する

というものでした。

今回追加したい新しいsceneは、1つ1つの料理じゃなくて、

料理(料理の写真、名前、評価)のリスト表示

が出来るようなものです。何かしらの項目をリスト表示するためのビューは用意されていて、table view(UITableViewと呼ばれています。

テーブルビュー(table view)は、それ専用のview controller(UITableViewController)で管理されています。新しいsceneを作ると書きましたが、これから作るsceneはこのテーブルビュー用のview controllerを基にして作ります。

UITableViewControllerは当然UIViewControllerのサブクラスになります。

ストーリーボードにテーブルビューのsceneを追加する

では早速テーブルビュー用のsceneを追加してみます。

  1. ストーリーボード(navigator areaでMain.storyboardを選択)を開く
  2. Utility area(右側)の下にあるObject Libraryから「Table View Controller」オブジェクトを検索
  3. 「Table View Controller」オブジェクトをドラッグして、既存のsceneの左側にドロップ

Xcode: Object LibraryからTable View Controllerを検索
公式ページにも書いてありますが、今回追加するのは「Table View Controller」(上の画像の一番上に表示されているオブジェクト)です。もしドラッグ&ドロップしても何も起こらない場合、「Table View」をcanvasにドラッグしている可能性があるので、要確認です。

Xcode: Table View Controllerをストーリーボードに追加した直後
「Table View Controller」をドラッグ&ドロップした後の画面が、上の画像のようになっているはずです。View controller間の位置は、ガイドが出るので簡単に調整可能です。警告が幾つか出ていますが、先に進むと解消するので今は無視します。

アプリを起動した時、最初に見る画面が料理全体のリストである方が、個々の料理表示であるよりも自然です。したがって、次は先程追加したtable view controllerが最初に表示されるsceneになるように設定します。

Table view controllerを最初に表示されるsceneとしてセットする

Xcode: Toolbarのボタン
2つのview controllerがあるので、もっとスペースが必要な場合はnavigator areaやutility areaを閉じます。ツールバーの右上にあるボタンで表示と非表示の切り替えが出来ます。Outline viewも邪魔なら隠します。

Xcode: Storyboard entry pointを動かす前
全部閉じると上の画像のようになります。右側のview controllerを指している矢印がありますが、これを「storyboard entry point」と呼びます。この矢印の指しているsceneが、アプリを起動した際に表示されるsceneになるので、矢印をドラッグしてtable view controllerの方へ持っていきます。

Xcode: Storyboard entry pointを動かす後
Storyboard entry pointを動かした後が上の画像のようになっています。これでtable view controllerがアプリ起動時に表示される最初のview controllerになったはずですので、シミュレータで確認してみます。

シミュレータを起動して表示される画面を確認

Xcode: Table view controllerが表示されるか確認
シミュレータを起動すると、空のリストが表示されているのが分かります。次は実際にアプリでこのテーブルを使えるように幾つか設定をします。

Table viewの設定

設定を箇条書きにすると、以下のようになります。今の所、各行の高さを設定するだけです。

  1. ストーリーボードでoutline viewを開く
  2. Outline viewで「Table View」を選択
  3. Table viewが選択された状態でutility areaからsize inspectorを開く
  4. 「Row Height」というラベルの場所に90をセットする

Xcode: Outline viewでtable viewを選択
Outline viewで「Table View」が見つからない場合は、「Table View Controller」の左側にある三角マークをクリックして中身を展開します(上の画像参照)。

Xcode: Table viewのsize inspectorでrow heightを設定
また、size inspectorは、(何度も出てきていますが)utility area上部の左から5つ目(右から2つ目)のメニューです(物差しのようなアイコン)。

自作テーブルセルをデザインする | Design Custom Table Cells

セル(cell)というのはWindowsのエクセルでも使われる表現で、テーブル要素の最小単位を指します。

テーブルのセル1つ1つを管理しているクラスがUITableViewCellで、こいつで各セルに描画されるコンテンツを決めることが出来ます。デフォルトの設定でも様々な状況で使えるみたいですが、FoodTrackerアプリではデフォルト設定では描画出来ないコンテンツがあるようなので、自作セルスタイルを定義する必要があります。

UITableViewCellのサブクラスを作る

Xcode: 新しいCocoa Touch Classファイルを選択
新規ファイルを作成し、iOSの「Cocoa Touch Class」を選択し、「Next」をクリック(上の画像参照)

新規ファイル作成はメニューから「File > New > File」(または⌘N

Xcode: UITableViewCellのサブクラスを作成
クラス名を「Meal」、サブクラスに「UITableViewCell」をセット。サブクラスをセットすると、名前が「Meal」から「MealTableViewCell」に自動的に変わります(上の画像参照)。Languageが「Swift」であることを確認して「Next」をクリック。

Xcode: 作ったソースファイル(RatingControl.swift)の保存先を決める
ファイル保存の画面になるので、デフォルト設定のまま(FoodTrackerにチェック、FoodTrackerTestはチェック無し)で「Create」をクリック。

Scene上のtable view cellはセルがどのように表示されるかのプロトタイプ

ここで一旦ストーリーボードを開きます。

Xcode: Table view controllerのtable view cell表示
Table view controllerを見ると分かりますが、table view cell(上の画像赤枠で囲んだ部分)は1つしか表示されていません。タイトルに「プロトタイプ」と書きましたが、これは「ここで表示されているセルの設定が他の全てのセルに適用される」という意味です。セルの設定を変更するために、ストーリーボード上のtable view cellとソースコード(今作った自作サブクラスのMealTableViewCell)を繋ぎます。

テーブルビューの自作セルを設定する

Outline viewからTable View Cellを選択

Xcode: Outline viewからtable view cellを選択
Outline viewから「Table View Cell」を選択します。

Table View Controller Scene > Table View Controller > Table View > Table View Cell

という感じで階層が深いです。「Table View Cell」が見つからない場合は、左側に表示されている三角ボタンを押して中身を展開します(上の画像参照)。

Attributes inspectorの設定

Xcode: Table View Cellのattributes inspector
「Table View Cell」が選択されている状態で、attributes inspector(utility area左から4つ目or右から3つ目)を開き、

  • Identifierのテキストフィールドに「MealTableViewCell」を入力
  • Selectionの項目で「None」を選択(上の画像で「Default」の部分)

Identifierの設定は後で重要になるので忘れずに設定しておきます。2番目の設定は、ユーザがセルをタップした際の挙動を決めるようです。「None」を選択すると、タップしても何も起こらないという設定です。

Size inspectorの設定

Xcode: Size inspectorの設定(table view cell)
Size inspector(utility area左から5つ目or右から2つ目)を開き、Row Heightのテキストフィールドに90をセット。数値をセットすると自動的に右側の「Custom」にもチェックが入りますが、もしチェックが入らなかったら自分で入れます。

Identity inspectorの設定

Xcode: Identity inspectorの設定(table view cell)
Identity inspector(utility area左から3つ目)を開き、Classのテキストフィールドに「MealTableViewCell」を入力(恐らく「M」を入力した時点で補完されるはずです)。

Table view cellをカスタマイズする準備完了

これで下準備が完了したので、ストーリーボード上でテーブルセルを直接カスタマイズすることが出来ます。最終的には、

Xcode: 料理のリスト表示のサンプル
このようなセルを作成する予定で、料理の名前、料理の写真、料理の評価を追加出来るような仕様です。料理の評価にはPart5で作ったRatingControlクラスを使います。

自作テーブルセルのUIをデザインする

UI要素の境界を強調する

Xcode: UI要素に境界線を引く
UI要素の境界線を強調してくっきり見せることで、テーブルセルの境界が見やすくなります。境界線を強調するにはメニューから

Editor > Canvas > Show Bounds Rectangles

にチェックを入れます。チェックを入れると、UIの境界に青っぽい実線が引かれるのでUI間の境目が見やすくなります(上の画像参照)。

Image viewオブジェクトをテーブルセルに挿入する

次に、utility areaの右下にあるObject LibraryからImage Viewオブジェクトを検索して、テーブルセルまでドラッグします。

Xcode: Table cellにimage viewを追加
ドラッグしたら、image viewを左端に持っていきます。Image viewの上下と左側をテーブルセルの境界に合わせ、大きさが正方形になるように調整します(上の画像参照)。おそらく正方形の大きさは89×89になるはずです。

Part4で作ったdefaultPhotoをimage viewに表示

Part4で作ったデフォルト画像(defaultPhoto)をimage viewに埋め込みます。もし作ってない場合はここで作ります。

Xcode: Image viewのattributes inspector、imageに「defaltPhoto」を選択
Image viewを選択した状態で、utility areaのattributes inspector(左から4つ目、右から3つ目)を開き、Imageというラベルが付いたテキストフィールドで「defaultPhoto」を選択します(上の画像参照)。

Xcode: Image viewにdefaultPhotoを表示した後
ImageにdefaultPhotoを選択した後の状態が上の画像のようになっているはずです。

ラベル(Label)を挿入

次はラベルを挿入します。Object Libraryからラベル(Label)を見つけて、テーブルセルにドラッグ&ドロップします。

Xcode: ラベルをtable cellに挿入。位置と大きさを調整
ドロップした後、ラベルをimage viewの右側、上マージンと左マージンの位置まで移動します。ラベルの大きさを調整して、ラベルの右側がテーブルセルの右マージンに一致する位置まで引き伸ばします(上の画像参照)。

Viewオブジェクトを挿入

評価システム(rating control)を導入するため、Viewオブジェクトを挿入します。Object LibraryからViewオブジェクトを見つけて、テーブルセルにドラッグ&ドロップします。

Xcode: Viewオブジェクトを挿入
Viewオブジェクトを挿入したら、utility areaでsize inspector(左から5つ目、右から2つ目)を開き、Height(高さ)を44にセットします。もしWidth(幅)が240でなければ240にセットします。Viewの大きさを調整したら、ドラッグしてViewの左端がラベルの左端(左マージン)に一致するように移動します(上の画像参照)。

ViewのクラスにRatingControlを指定

Xcode: クラスにRatingControl
次に、Viewオブジェクトを選択した状態で、identity inspector(左から3つ目、右から4つ目)を開き、Classというラベルのテキストフィールドで「RatingControl」を選択します(上の画像参照)。もし「RatingControl」が選択肢として出てこない場合、Viewオブジェクトがちゃんと選択されているかどうか確認しましょう。

User Interaction EnabledをOFFにする

Xcode: ViewのUser Interaction Enabledチェックを外す
最後に、attributes inspector(左から4つ目、右から3つ目)を開き、「User Interaction Enabled」のチェックボックスのチェックを外します(上の画像参照)。一覧として表示中、評価システムは単に表示するためだけのUIなので、評価システムをタップすると評価が変わるといったinteractiveな機能はOFFにしておきます。

ここまでの設定で、テーブルセルUIの(ストーリーボード上での)見た目は

Xcode: テーブルセル設定後
こんな感じになっているはずです。

シミュレータで動作確認

Xcode: テーブルセル編集後、シミュレータで動作確認
シミュレータで動作確認してみます。確かにテーブルセルの高さ(縦の長さ)が伸びているのが分かりますが、色々と追加したオブジェクト(image view等)は全く見た目に反映されていません。

公式ページに色々と書いてありますが、

In a storyboard, a table view can be configured to display static data (supplied in the storyboard UI) or dynamic data (supplied by the table view controller logic). The table view defaults to using dynamic data, and because you’ll need to load data in code, this is what you want it to do—you just haven’t implemented that behavior yet. This means that the static content you supplied in the storyboard doesn’t show up at runtime, so you can’t see it when you run the app—until you implement the data model behind it.

どうやらtable viewに表示するデータは

(1)static data(ストーリーボード上のUIで提供)と
(2)dynamic data(table view controllerから提供)

の2つがあるらしく、デフォルトでは後者を表示するような仕様になっているようです。今我々が一生懸命実装したのは前者らしく、後者を実装していないので表示されていない、という状況です。シミュレータでちゃんと表示させるには、前回作ったデータモデルを実装する必要があるみたいです。

今の所シミュレータでは、ストーリーボード上で実装したUIを確認することが出来ないようなので、assistant editorを使ってプレビューします。

自作テーブルセルのUIをプレビューする

いつものように、assistant editorを開き、navigator areaとutility areaが邪魔なら閉じます。Assistant editorのeditor selector bar(assistant editor上部にあるメニューバー)で、「Automatic」から「Preview > Main.storyboard(Preview)」に切り替えます。

Xcode: Assistant editorでストーリーボードのプレビュー
ストーリーボードのプレビューを表示した状態が上の画像です。プレビューを見ると、設定したUIの見た目がちゃんと反映されているのが分かります。もしassistant editorにテーブルビューのsceneが表示されない場合は、左側でテーブルビューsceneを選択します。

プロジェクトにイメージを追加する | Add Images to Your Project

テーブルビューにデフォルトで料理のリストを表示したいので、料理の画像を追加しておきます。画像の追加方法は、基本的にはPart5で評価システム用の画像(星形の画像2つ)を追加した時と同じです。

  • Standard editorに戻す(navigator areaとutility areaを開く)
  • Asset catalogを開く(project navigatorでAssets.xcassertsを選択)
  • Asset catalogのメニューで+ボタンをクリック、「New Folder」を選択
  • フォルダの名前を「Sample Images」に変更する(下の画像参照)

Xcode: Asset catalogの新規フォルダ「Sample Images」を追加

  • フォルダ「Sample Images」が選択された状態で、再び+ボタンを押して「New Image Set」を追加
  • 追加したimage setの名前を適当に決める。後でソースコード中で使うので、覚えやすい名前にする
  • 追加したい画像をimage setの2xの枠にドラッグ&ドロップ。

公式ページに3つサンプル画像があるので、今回はそれを使います。

Xcode: サンプル画像を3つ追加した後
サンプル画像を全て追加すると、上の画像のようになっているはずです。

テーブルセルのUIとコードを繋ぐ | Connect the Table Cell UI to Code

ここまででテーブルのセルをカスタマイズして、ストーリーボード上でUIを色々用意しました。所謂「dynamic data」を表示するためには、まず作ったUIをアウトレット(outlets)としてソースコード(MealTableViewCell.swift)と繋ぐ必要があります。

アウトレットで繋げるための準備 | Assistant editorでMealTableViewCell.swiftを開く

ストーリーボード(Main.storyboard)を開いて、テーブルビューセルのラベルを選択しておきます。次に、assistant editorを開いて、スペース確保のためにnavigator area、utility area、さらにoutline viewなどを隠します。

Xcode: Assistant editorでMealTableViewCell.swiftを開いた状態
ストーリーボードのプレビューが開いていると思うので、editor selector bar(assistant editor上部にあるメニューバー)から、「Automatic > MealTableViewCell.swift」を選択して、ソースコードを開きます(上の画像参照)。

ラベルのアウトレット作成

Part3でアウトレットを作りましたが、やり方は全く一緒です。ラベルのアウトレット作成だけ少し丁寧に説明します。

プロパティを定義するのでMARKコメントを追加しておく

プロパティを追加することになるので、いつもの

// MARK: Properties

コメントを追加しておきます。

アウトレット作成 | Control+ドラッグでUIとソースコードを繋ぐ

Xcode: ラベルをアウトレットとしてコードに繋ぐ
ストーリーボードでラベルを選択した状態にして、ラベル上でcontrlを押しながらソースコードの方にドラッグすると、UIとコードを繋ぐ実線が出ます(上の画像参照)。先程追加した// MARK:コメント直下にドロップします。

Xcode: Outletの設定(ラベル)
ドロップするとダイアログが出て来る(上の画像参照)ので、名前に「nameLabel」と入力します。残りの項目はデフォルトのままで「Connect」をクリック。

ソースコードを見ると、

@IBOutlet weak var nameLabel: UILabel!

というアウトレット(@IBOutlett付きプロパティ)が出来ているはずです。

同様に、写真用のイメージビューと、評価システム用のビューに対しても、アウトレットを作ります。

写真と評価システム用アウトレットを作成

手順はラベルの場合と全く同じです。

写真

  • Image viewをクリックして選択された状態にする
  • Control+ドラッグでソースコード(nameLabelの下)にドロップ
  • ダイアログに名前「photoImageView」を入力
  • 残りの項目はデフォルトのまま「Connect」をクリック

評価システム

  • 評価システム用ビューをクリック
  • Control+ドラッグでソースコード(photoImageViewの下)にドロップ
  • ダイアログに名前「ratingControl」を入力
  • 残りの項目はデフォルトのまま「Connect」をクリック

Xcode: Table view cellのoutletを追加
アウトレットを全て追加した後の状態が、上の画像のようになっているはずです。

初期データのロード | Load Initial Data

料理の情報を画面に表示したいのですが、表示するには料理のデータが必要です。料理のデータを使う場合、そのデータ自身をロードしておく必要があります。データのロードする場所として提供するのがview controllerです。

今回はテーブルビュー用のコントローラーなので、UITableViewControllerのサブクラスを用意します。また、Part6で料理の情報としてデータモデルMealクラスを作りましたが、テーブルビューでリスト表示するので配列を使います。

UITableViewControllerのサブクラスを作る

iOS用のCocoa Touch Classを作成、UITableViewControllerのサブクラス「MealTableViewController」を作ります。手順は以下の通りです。

Cocoa Touch Classを作成

  1. 新規ファイルを作成(メニューからFile > New > File or ⌘N
  2. iOSの「Cocoa Touch Class」を選択
  3. 「Next」をクリック

クラス名、親クラスの決定

  1. 「Class」のテキストフィールドに「Meal」とタイプ
  2. 「Subclass of」では「UITableViewController」を選択。これでクラス名が「MealTableViewController」に変わる
  3. 「Also create XIB file」にはチェックを入れない
  4. 「Language」が「Swift」になっていることを確認して「Next」をクリック

Xcode: MealTableViewControllerの作成

デフォルト設定のままファイルを保存。

ファイル保存先はデフォルト(自分のプロジェクトディレクトリ)、グループ名は「FoodTracker」、ターゲットでは「FoodTracker」が選択された状態、テスト用コードは選択されていない状態。

ソースコード「MealTableViewController.swift」が出来ているはずです。View controllerが出来たので、次は料理のリストを配列で作ります。

初期データをロードする

配列mealsを初期化

MealTableViewController.swiftを編集していくので、standard editor表示に戻します(assistant editorからstandard editorに切り替え、navigator areaとutility areaを開く)。

データモデルMealの配列を作りますが、いつものように// MARK:コメントも追加しておきます。

// MARK: Properties

var meals = [Meal]()

プロパティmealsは変数(var)で宣言されており、宣言と同時に空の配列で初期化されています。配列の型は[Meal]です。変数なのは後で中身を追加したり、変更するためです。

Mealオブジェクトを作成、配列に要素として追加

viewDidLoad()メソッドの下に、料理データをロードするためのメソッドloadSampleMeals()を定義します。

func loadSampleMeals() {
    let photo1 = UIImage(named: "meal1")!
    let meal1 = Meal(name: "Caprese salad", photo: photo1, rating: 4)!

    let photo2 = UIImage(named: "meal2")!
    let meal2 = Meal(name: "Chicken and Potatoes", photo: photo2, rating: 5)!

    let photo3 = UIImage(named: "meal3")!
    let meal3 = Meal(name: "Pasta with Meatballs", photo: photo3, rating: 3)!

    meals += [meal1, meal2, meal3]
}

3つのMealオブジェクトを作って、それを配列mealsに要素として追加しています。公式マニュアルの例を流用していますが、注意点が2点あります。

1点目は、写真の名前(パラメータで指定されている"meal1"など)はasset catalogに追加した写真の名前と同じにしなければならない、ということ。2点目は、上のコードで使ったUIImageMealの初期化子は両方ともオプショナル型を返すので、最後の!を忘れずに付けること、です。

名前を間違えるとコンパイルエラーにはなりませんが、後でシミュレータを使って動作確認をする時にクラッシュします。

viewDidLoad()からloadSampleMeals()の呼び出し

デフォルトのviewDidLoad()には、コメントアウトされたコードがありますが、今回は不用のようです。コメントアウトされたコードを全て消して、loadSampleMeals()を実装します。

override func viewDidLoad() {
    super.viewDidLoad()

    // Load the sample data.
    loadSampleMeals()
}

プロジェクトをビルドしエラーが無いことを確認する

プロジェクトをビルド(Product > Build or ⌘B)して、コンパイルエラーが無いことを確認してから先に進みます。

データを表示する | Display the Data

テーブルビューに表示したいデフォルトの料理データを作ったので、早速それらをUIに表示させたいと思います。

Data sourceとdelegate

公式ページでも少し詳しく説明されていますが、動的に入力されるようなデータを取り扱う場合、テーブルビューは

Data source(UITableViewDataSource
Delegate(UITableViewDelegate

という2つの補助機能を使います。補助機能と書きましたが、これらは2つともプロトコルです。

UITableViewDataSourceは表示するデータをテーブルビューに提供するためのプロトコルで、テーブルビューでデータを表示する場合には実装が必須なメソッドが幾つかあります(後述)。

一方のUITableViewDelegateは、テーブルセルの状態遷移管理や見た目の管理等々を行っています。Delegateで用意されているメソッドは全てオプションなので、必ずしも実装する必要はありません。後述しますが、実際今回実装されているメソッドは全て、UITableViewDataSourceから要求されているメソッドです。

また、公式ページにも書いてありますが、

By default, UITableViewController and its subclasses adopt the necessary protocols to make the table view controller both a data source (UITableViewDataSource protocol) and a delegate (UITableViewDelegate protocol) for its associated table view. Your job is to implement the appropriate protocol methods in your table view controller subclass so that your table view has the correct behavior.

これら2つのプロトコルはUITableViewControllerがデフォルトで採用しているプロトコルなので、改めて私達が明示的に採用する必要はありません。我々がやらないといけないのは、これらプロトコルが用意しているメソッドを正しくサブクラスに実装して、その結果として(表示したい)データをきちんと表示することです。

実装する3つのメソッド

今回実装するメソッドは3つです。先程述べましたが、全てUITableViewDataSourceのメソッドです。

override func numberOfSections(in tableView: UITableView) -> Int
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

MealTableViewControllerを見ると分かりますが、1つ目と2つ目のメソッドはデフォルトで実装されており(中身は空)、3つ目のメソッドは実装されていますがコメントアウトされています。

API Referenceのソースコードを見ると、3つ目のメソッドは「Required」と書いてあるので実装が必須かと思いましたが、デフォルトでコメントアウトされているということは、実装しなくても良いということなのかもしれません。

では順番に実装していきます。

テーブルビューでセクションを表示

先ずは最初のメソッドnumberOfSections(in:)で、これはテーブルビューのセクション数を返すメソッドです。「セクション」というのはテーブルビューセルをグループ化するために使用されます。大量のデータを取り扱うテーブルビューでは便利で、階層構造をイメージすると分かりやすいかもしれません。

FoodTrackerでは料理のリストを並べて表示しますが、それ全体を1つのセクションとして取扱います。したがって、セクション数は1ですから、デフォルトでは

override func numberOfSections(in tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 0
}

となっていたメソッドを

override func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}

のように書き換えます。返り値を0から1に変更、またコメントを削除しています。

テーブルビューの行数を返す

次のメソッドはtableView(_:numberOfRowsInSection:)で、これはセクション1つが持つ行数を返すメソッドです。FoodTrackerのテーブルビューでは、料理のリストを表示しますが、各料理に対して1行を割り当てますので、

1セクション当たりの行数 = meals配列中のMealオブジェクト要素数

になります。

したがって、デフォルトでは

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return 0
}

となっているソースコードを

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return meals.count
}

と書き換えます。countプロパティは配列の要素数を返します。

テーブルビューセルの設定と表示

コンテンツを表示するセル

3つ目のメソッドtableView(_:cellForRowAt:)は、テーブルビューの「セル(cell)」を設定する場所です。セルはテーブルビュー各行に1つ用意されていて、各セルは表示されるコンテンツとそのレイアウトを決めています。

メソッドtableView(_:cellForRowAt:)を使うと、実際に画面に表示されるテーブルの行だけに対して、セルに表示させるコンテンツを設定することが出来るようです。FoodTrackerのようにそもそも行数が少ない場合は余り恩恵がありませんが、行数が多いテーブルビューの場合、画面外で表示されていない行に対してもセルにコンテンツを設定するのは無駄になります。

メソッドtableView(_:cellForRowAt:)のコメントを外す

tableView(_:cellForRowAt:)はデフォルトではコメントアウトされているので、コメント/* */を外します。デフォルトのメソッドは

override func tableView(_ tableView: UITableView,
  cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)

    // Configure the cell...

    return cell
}

のような中身になっています。

一行目は、

let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)

メソッドの名前が難しいですが、セルを取り出して定数cellに格納しているだけです。ただし、デフォルト名"reuseIdentifier"はプレースホルダーとしてセットされているだけなので、コードを改良しないといけません。

次にやるべきことは、このプレースホルダーとしてセットされている名前を、自作セル「MealTableViewCell」の識別子(identifier)に変更することです。その後セルを設定するためのコードを

// Configure the cell...

以下に追加していきます。

IdentifierをMealTableViewCellに変更

MealTableViewCellを作成した時に、attribute inspectorでidentifierを設定したので、それを指定します。

// Table view cells are reused and should be dequeued using a cell identifier
let cellIdentifier = "MealTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)

cellをMealTableViewCellにダウンキャスト

UITableViewのメソッドdequeueReusableCell(withIdentifier:for:)の返り値はUITableViewCellです。今、cellとしては自作セルMealTableViewCellを使っていますが、これはUITableViewCellのサブクラスですから、ダウンキャスト出来ます。

let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! MealTableViewCell

セルのプロパティをセット

セルの準備が出来たので、サンプルデータとして用意したMealオブジェクトの配列からデータを取り出してセルに代入します。

// Fetches the appropriate meal for the data source layout.
let meal = meals[indexPath.row]

cell.nameLabel.text = meal.name
cell.photoImageView.image = meal.photo
cell.ratingControl.rating = meal.rating

メソッド全体

全ての実装が完了したら、メソッドtableView(_:cellForRowAt:)は、

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // Table view cells are reused and should be dequeued using a cell identifier
    let cellIdentifier = "MealTableViewCell"
    let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! MealTableViewCell

    // Fetches the appropriate meal for the data source layout.
    let meal = meals[indexPath.row]

    cell.nameLabel.text = meal.name
    cell.photoImageView.image = meal.photo
    cell.ratingControl.rating = meal.rating

    return cell
}

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

MealTableViewController.swiftとストーリーボードのsceneを繋ぐ

UIに料理データを表示するためには、MealTableViewControllerとストーリーボードのsceneを繋ぐ必要があります。

ストーリーボードを開いてクラス名をMealTableViewControllerにセット

Xcode: Table view controllerをクリックした状態
ストーリーボードを開いて、table view controllerのscene dock(sceneの上部、ツールバーのような部分)をクリックします。クリックするとscene全体が青い実線で囲まれます。上の画像だと少し分かりにくいかもしれませんが、外枠が薄い青色で囲まれているのが分かります。

Identity inspectorを開いて、Classラベルのテキストフィールドで「MealTableViewController」を選択。

Xcode: Identity inspectorでMealTableViewControllerをセット
これでストーリーボードのテーブルビューとMealTableViewControllerが繋がりました。

シミュレータで確認

シミュレータを起動して、テーブルビューのセルにコンテンツが表示されるかどうか確認します。
Xcode: シミュレータでtable viewを表示

viewDidLoad()で追加した項目(実際はloadSampleMeals())がちゃんと表示されていることが分かります。ただし、一番上のセルがステータスバーと被っています。これは次のパート(Part8)で直すようです。

ナビゲーション機能を実装するための準備 | Prepare the Meal Scene for Navigation

次回Part8ではナビゲーション機能を実装するようですが、その準備のため、必要無くなったコードを削除したり名前を変更します。

不用なソースを削除

ストーリーボード上のラベルを削除

ストーリーボードの最初に作ったsceneを開くと、

Xcode: ラベル付きMeal scene
現状はこのようになっているはずです。一番上の「料理名」ラベルは不用なので削除します。削除するには「料理名」と書いてある部分をクリックして、deleteボタンを押すだけです。

Xcode: ラベル無しMeal scene
「料理名」ラベルを削除すると上記の画像のようになっているはずです。ラベルを削除すると、それ以外のUIが自動的に再配置されます。

ViewController.swiftのラベル関連コードを削除

次はViewController.swiftを開いて、ラベルに関連するコードを削除します。ラベルのプロパティはmealNameLabelなので、これに関連するコードは

@IBOutlet weak var mealNameLabel: UILabel!
....
func textFieldDidEndEditing(_ textField: UITextField) {
    mealNameLabel.text = textField.text
}

です。メソッドtextFieldDidEndEditing(_:)自体は残すので、中身

mealNameLabel.text = textField.text

だけを削除します。また、アウトレット

@IBOutlet weak var mealNameLabel: UILabel!

も削除します。

ViewController.swiftの名前を変更

今回のパートで新しいview controller(MealTableViewController.swift)を作ったので、最初に作ったViewController.swiftにも意味のある名前を付けます。

Project navigatorでファイル名を変更

Project navigatorでViewController.swiftを選択し、もう一度クリックするか、またはreturnキーを押します。

Xcode: ViewController.swiftのファイル名を変更
そうすると上の画像のように、名前を変更出来る状態になるので、名前を「MealViewController.swift」に変更します。

クラス名をMealViewControllerに変更する

ファイル名を変更しただけでは実装されているクラス名は変わりませんので、明示的に変更する必要があります。今、MealViewController.swiftのクラス宣言は、

class ViewController: UIViewController, UITextFieldDelegate,
    UIImagePickerControllerDelegate, UINavigationControllerDelegate {

のようになっているはずですから、これを

class MealViewController: UIViewController, UITextFieldDelegate,
    UIImagePickerControllerDelegate, UINavigationControllerDelegate {

と変更します。

またソースコードを一番上のコメントが

//
//  ViewController.swift
//  FoodTracker
....

となっているはずですから、これもMealViewController.swiftに変更しておきます。

Identity inspectorのクラス名をMealViewControllerに変更

最後にストーリーボードを開いて、料理用のsceneを選択、identity inspectorを開きます。

Xcode: Identity inspectorでクラス名をMealViewControllerにセット
Identity inspectorのクラス名で「MealViewController」を選択します(上の画像参照)。

ビルドして動作確認

ビルドしてシミュレータで動作確認して以前と同様に動くことを確認します。現時点では今変更を加えたMealViewControllerのsceneに到達する方法がありませんが、次回のPart8で実装するナビゲーション機能を使ってこれを解消します。

まとめ

今回は新しいsceneを作って、テーブルビュー型のUIを用意し、料理のリスト表示が出来るようになりました。ただし、最初に作った料理用sceneにアクセス出来なくなったので、次回はナビゲーション機能を実装してこれを解消します。

「Start Developing iOS Apps (Swift)」でFoodTracker Appを実際に作ってみる その8[ナビゲーションを実装する]
公式にある「Start Developing iOS Apps (Swift)」を使って実際にFoodTracker Appを作ってみる、と...
スポンサーリンク
広告1
広告1

シェアする

フォローする