Swiftで遊ぼう! on Hatena

あしたさぬきblogでやってた初心者オヤジのiOSプログラミング奮闘記がHatenaに来ました

Swiftで遊ぼう! - 381 - Create a Table View

チュートリアル索引に戻る→

2015年10月24日改訂:環境環境:Xcode7.1Swift2.1

さて、今日はTable Viewの作成に入ります。何度か取り組んでいるUITableViewとUITableViewControllerの関係や実装の仕方、そしてカスタムTable View Cellの実装法など、一度取り組んだ内容をアップルのチュートリアルで復習します。

developer.apple.com

ここで習得する項目は次の通りです。

  • 2つめのストーリーボード・シーンを作る
  • table viewのキー・コンポーネントを理解する
  • カスタムtable view cellを作る
  • table viewのdelegatesとdata sourcesの役割を理解する
  • データの保持と利用のためにアレー型を使う
  • table biewでダイナミックデータを表示する

これらの項目は既に理解できているはずです。すべて聞き覚えがあります! 覚えているはず... あれ、勉強したはずなのだが...

これが親父脳です。いやあ他の親父さんに失礼かもm(_ _)m 私だけ? すっかり忘れているの(^_^;)

さあ、復習です。なんとなく理解しているので直ぐわかるでしょう。と楽観的に取りかかります。

(^^;)

プロジェクト・ライブラリーからMain.storyboardを選んで、オブジェクト・ライブラリからTable View Controllerを選択します。Table ViewではなくTable View Controllerです。それをViewControllerの左横にドラッグ&ドロップします。

次にStoryboard entry pointをViewControllerからTableViewControllerにドラッグして移します。

f:id:yataiblue:20151026205929j:plain

次にTable Viewの設定を変えます。

まずTable Viewを選択するためにドキュメント・アウトラインを使います。Main.storyboard上で複数のオブジェクトが視覚的に重なっていると選択しづらくなりますが、ドキュメント・アウトラインを使うと間違えることなく目的のオブジェクトを選択できます。そこからTable Viewを選んで、サイズ・インスペクタを開きます。Row Heightを「90」にしてデフォルトの設定より高さを高くします。

さらにカスタムTable View Cellを作っていきます。

カスタムTable Cellは、UITableViewCellクラスから拡張させます。

Table View Cellは、デフォルトスタイルがいくつか用意されているという話は以前しました。

yataiblue.hatenablog.com

今回はカスタムスタイルのcellデザインをしていきます。

メニューの「FIle」から「New」の「File...」を選びます。

ダイアログの左側の「iOS」の下にある「Source」を選んで、右側のペインから「Cocoa Touch Class」を選んで「Next」ボタンを押します。

次の「Class field」に「Meal」とタイプして入力だけしておきます。

「Subclass of」から「UITableViewCell」を選ぶと、どうでしょう! クラスのタイトルが自動的に「MealTableViewCell」と変化しました! こういう風に分かりやすい名前に切り替える機能があるんです。

言語は「Swift」のままにします。

「Next」ボタンを押して適切な場所にセーブします。前のプロジェクトでtestターゲットにマークを入れましたが、今回はデフォルトのままtestは選ばないでおきます。

「Create」すれば、プロジェクト・ナビゲータにMealTableViewCell.swiftファイルができます。

次にMain.storyboardを開きます。table view controllerをみると、1番上の1つだけtable view cellが表示されています。これは、他のcellすべてに共通するプロトタイプになります。これをデザインすることでcell全体にデザインが反映します。

ここに表示されているtable view cellは、まだジェネリックな存在なのでこれから実装していきます。

ドキュメント・アウトラインからTable View Cellを選びます。

アトリビュート・インスペクタを選択して「Identifier」の空欄に「MealTableViewCell」とタイプして入力します。重要なステップですが後で説明します。

「Selection」の項目は「None」を選ぶことで、cellを選択しても視覚的にハイライトされなくなります。

次にサイズ・インスペクタを選択して、「Row Height」のサイズを「90」にします。「Custom」チェックボックスは選択されている必要があります。

次にアイデンティティ・インスペクタを選択します。「Class」から「MealTableViewCell」を選びます。これでいわゆるコンフィギュレーション(設定)はできました。

次にカスタムtable cellをデザインしていきます。

メニューの「Editor」から「Canvas」の「Show Bounds Rectangles」を選んで、オブジェクトの境界線を表示させ、table cellのエレメントを整列させやすくします。

食事のイメージを扱うので、オブジェクト・ライブラリからImage Viewを選んでcellにドラッグ&ドロップします。ここに食事のイメージを表示させます。ドラッグ&ドロップした状態は大きくcellからはみ出しているでしょう。リサイズしてcell内に収まるように小さくしますが、多分88×88ポイントです。そして、デフォルトフォトをここにセットさせます。

f:id:yataiblue:20151026220635j:plain

Image Viewを選択した状態でアトリビュート・インスペクタを選んで、「image」から「defaultPhoto」を選びます。

次にオブジェクト・ライブラリからlabelをcellにドラッグ&ドロップしてHIG(ヒューマンインターフェースガイドライン)に沿ってリサイズします。

f:id:yataiblue:20151026222123j:plain

次にViewをcellにドラッグ&ドロップして、選択された状態でサイズ・インスペクタを選択、Widthを「240」、Heightを「44」にします。

f:id:yataiblue:20151026222655j:plain

アイデンティティ・インスペクタを選んで、Classから「RatingControl」を選びます。

再びアトリビュート・インスペクタから「Interaction」にある「User Interaction Enabled」のチェックボックスを外します。このViewクラスはインタラクティブな機能を実装していますが、このCell内で必要ありません。

これをラン(Cmd + R)しても空欄の幅広Cellが表示されるだけでレイアウトは見えません。

f:id:yataiblue:20150729071855j:plain

Table Viewは、静的(スタティック)、もしくは動的(ダイナミック)なデータのどちらかを表示する機能を備えています。デフォルトで動的データを使用するようになっているため、コードを使ってデータを実装しなければなりません。しかし、まだコードできていないので、Table Viewは、実行時にコンテンツを表示できないんです。デザインのプレビューをみるためにアシスタント・エディタを開いて、エディタ・セレクター・バーから「Automatic」> 「 Preview」 > 「Main.storyboard (Preview)」を選択するとプレビューが確認できます。

f:id:yataiblue:20151027081628j:plain

cellに表示するサンプルイメージをいくつかプロジェクトに加えます。

まずアプリを立ち上げた時、デフォルトでイメージ・データをロードさせます。このイメージを用意するのですが、本家アップルのチュートリアルの最後にダウンロードするためのリンクがあるので手に入れるといいでしょう(本家アップルのダウンロードリンク)。←直リンクボタンを作っちゃいけないかもしれないので、駄目だったら指摘してください。

3枚のイメージが用意されているのでこれをプロジェクトに取りこんでいきます。

プロジェクトでイメージを利用する場合、asset catalogを使います。これはプロジェクトナビゲータにある「Assets.xcassets」のことです。

下記にある(+)ボタンを押して、「New Folder」を選び、できあがった「New Folder」をダブルクリックします。名前を「Sample Images」に変更します。

このフォルダー内に新しいimage setを3つ作ります。それぞれ、「meal1」「meal2」「meal3」とします。

それぞれの「2x」スロットにダウンロードしたイメージをドラッグ&ドロップします。

Main.storyboard上のジェネリックなオブジェクトをコードに組み込んで使用できるようにします。

MealTableViewCell.swiftファイルをアシスタントエディタを使って開きます。

class MealTableViewCell: UITableViewCell {

ここに // MARK: Propertiesを加えます。

そしていつものようにstoryboard状のオブジェクトを「// MARK: Properties」の下に「Ctrl + ドラッグ」して名前を付けていきます。Labelは「nameLabel」、ImageViewは「photoImageView」、そしてカスタム・ビューのRatingControlに「ratingControl」と名前を付けます。

class MealTableViewCell: UITableViewCell {

 // MARK: Properties
 @IBOutlet weak var nameLabel: UILabel!
 @IBOutlet weak var photoImageView: UIImageView!
 @IBOutlet weak var ratingControl: RatingControl!

これでcellの準備はできたのでTableViewControllerの準備をしていきます。プロジェクトナビゲータをみてもコードがありません。早速メニューの「File」>「New」>「File...」を選びます。

左ペインのiOSにあるSourceを選び、右ペインの「Cocoa Touch Class」を選択して、Classフィールドに「Meal」と最初に入力して、「Subclass of:」から「UITableViewController」を選ぶと、Class名は自動的に「MealTableViewController」に変化するのでそのままNextボタンを押して作成します。プロジェクトナビゲータにMealTableViewController.swiftができます。

次のステップは、MealTableViewControllerとMealTableViewCellを関連付けていく作業です。「あれ? UITableViewクラスを実装しなくていいの?」 なんて疑問が生まれた人はまだプログラム初心者です。

iOSフレームワークで用意されているオブジェクトの関係性を理解していないと、Table Viewの扱いが理解できなくなるので、過去の記事を参考にして考察を加えます。

yataiblue.hatenablog.com

少し勉強をしてiOSプログラミングが分かってきた人間として意見を言います。実はiOSFrameworkの利用法に多様性があり、それが初心者を混乱に陥れる一因にもなっています。

Table Viewの実装にしても、ViewControllerをベースにして実装することもできるし、当然のようにTableViewControllerをベースにして実装することもできます。実は、iPhoneにアプリを動かすために独自のインターフェイスを組み込んで自分だけの世界を作りあげる事も可能なんです。ユーザーインターフェイス(UI)を統一させ、直感的に操作ができるようにするという考えは重要なのでiOSフレームワークは用意されています。しかし、開発の歴史的な変化も内包しているためこの多様性はかなり複雑になっています。

プログラミング初心者は、これが理解できないんです。初心者向けに用意されているチュートリアルが唯一無二の方法じゃないとどうして説明してくれていないんでしょう。少なくともスタンフォード大学のポール先生は言及してますね。彼のおかげでこのバリエーションの存在に気がつかされました。彼の講義は素晴らしいのですが、英語がある程度わからないと理解できないのが難点です。

それでもチュートリアルをいくつかこなせば、この多様性も少しは理解できるんじゃないかと思います。私も1年勉強してやっと理解できたところです(^^;) 今はプログラミング中級者に向けて邁進しはじめたところです。

これまで取り組んでいたTable View実装の手順は、View Controllerをベースにして、Table Viewを組み込んでいました。しかし、アップルのチュートリアルはTable View Controllerを使うので、アップルのチュートリアルの方が自然な教え方のような気はします。

しかし、プログラミング初心者はこの2つの方法の違いが分からないかもしれません。どうしてView ControllerにTable Viewを組み込むチュートリアルが存在するのか? 実は理由があります。Controller系のクラスとView系クラスの関係を繋ぐ手法にプロトコールとデリゲーションを使うからです。

もう何度も繰り返して復習をしたので私は理解しました。しかし、初心者にデリゲーションとプロトコールを理解させるために、View Controllerにコードを書いて実装させていくステップを組んだのだと思います。View Controllerを継承したTable View Controllerの場合、デフォルトでTable Viewを持っているうえに、デリゲーションとプロトコールが既に設定されているため説明には不向きなんです。

私は逆にTable View Controllerを使うのが初めてなので、最初違和感を感じましたが、純粋にTable Viewを使うのならTable View Controllerを使う方が明らかに便利です。

ではアップルのチュートリアルに戻ります。

Main.storyboardにあるMealTableViewControllerを選択してドキュメント・アウトラインを確認します。

f:id:yataiblue:20150731175928j:plain

先ほど話したようにTable Viewは既に存在するし関連付けもされています。ここのリンクにあるデリゲーションとプロトコールの6つのステップをみながら考えてみます。

ステップ1と4:View ControllerにTable Viewを繋げる場合、Table Viewクラスの用意しているUITableViewDataSourceとUITableViewDelegateの2つのプロトコールを意識して、ViewControllerに準拠させる必要があります。しかし、TableViewControllerは既にこのプロトコールを内包しているのでクラス宣言時に準拠させる必要が無いんです。

ステップ2と3:このステップは、基本的に自分でプロトコールをデザインする時に重要なステップですが、既にできあがったプロトコールを利用する時は意識する必要がありません。これはViewControllerでもTableViewControllerでも同じです

ステップ5:これはViewControllerで実装する場合に必須で、コードで書く方法とXcodeで設定する方法があります。しかし、Table View Controllerはこの設定もデフォルトでされているので何もしなくていいんです。

ステップ6:結局のところTableViewControllerを使用する場合、このメソッドのコーディングだけでいいわけです。

このようにTableViewControllerを使うとTableViewを意識しないことになり、初心者はTableViewを考えなくなってしまうかもしれません。TableViewControllerとTableViewCellの間にTableViewがあることを意識させるために、ViewControllerを使ってTableViewの実装をさせるチュートリアルを作っていたと思われます。

理屈がわかったところで、MealTableViewController.swiftのコードを加えていきます。

デフォルトで表示するデータは、デモでありがちですが、MealTableViewController(コントローラー)で持ちます。データは普通MVCモデルを考えれば分離ですね。

class MealTableViewController: UITableViewController {

 // MARK: Properties
 var meals = [Meal]()

Mealクラスの空っぽアレーができました。

ここにサンプルデータを持たせるメソッドを作ります。まずインスタンス生成時に注意が必要なのは、UIImageはオプショナル宣言をしているので強制アンラップ「!」が必要です。そしてMealのイニシャライザも失敗許容イニシャライザなので、強制アンラップ「!」が必要になります。

func loadSampleMeals() {
 let photo1 = UIImage(named: "meal1.jpg")!
 let meal1 = Meal(name: "カプレス・サラダ", photo: photo1, rating: 4)!
 let photo2 = UIImage(named: "meal2.jpg")!
 let meal2 = Meal(name: "チキン&ポテト", photo: photo2, rating: 3)!
 let photo3 = UIImage(named: "meal3.jpg")!
 let meal3 = Meal(name: "ミートボールパスタ", photo: photo3, rating: 5)!
 meals += [meal1, meal2, meal3]
}

そしてこのメソッドをviewDidLoad()メソッドで呼べばいいんです。

override func viewDidLoad() {
 super.viewDidLoad()
        
 // Load the sample data.
 loadSampleMeals()
}

これで準備ができたので、デリゲーションメソッドの実装をします。

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

「override」キーワードが付くのはTableViewControllerなので当然です。ViewControllerに実装するなら必要無いです。この必須メソッドはセクションの数を返します。今回は「1」を返します。

もう一つの必須メソッドは行数です。これはデータの個数を返せばいいので、アレーの個数を返すメソッド「count」を使うので次のようになります。

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

そして最後にMealTableViewCellをインスタンス化して表示するメソッドをオーバーライドします。tabelViewが持っているcellを実体化するメソッド「dequeueReusableCellWithIdentifier()」は、Identifierを使ってインスタンスを生成します。この章の前半で、MealTableViewCellのIdentifierを設定しましたが、それを使って実体化させます。

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
 
let cellIdentifier = "MealTableViewCell"
// MealTableViewCellに付けたIdentifierをここで使います。
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, 
                           forIndexPath: indexPath) as! MealTableViewCell
let meal = meals[indexPath.row]
        
cell.nameLabel.text = meal.name
cell.photoImageView.image = meal.photo
cell.ratingControl.rating = meal.rating
        
return cell
}

これをラン(Cmd + R)させると下のようにカスタムCellが表示されました。今までの画面イメージは「iPhone5s」でした。しかし、振り返ってみるとMealTableViewCellに関して、レイアウトの調整をしていないのでiPhone5sでは大きくズレます。ということでアップルチュートリアルのイメージに近づけるために「iPhone6」でシュミレートしたイメージが次です。テーブル・ビューとステータス・バーが少しオーバラップしてますが、これは次の課題で解消していきます。

f:id:yataiblue:20151027152812j:plain

最後に次の章のNavigationのためにコードを微調整します。ビューコントローラーで用意しているシーンで使用しないオブジェクトやコードを消します。

先ず、Main.storyboardでビューコントローラーを選択します。

f:id:yataiblue:20151027174413j:plain

「nameLabel」は必要ないので、「Meal Name」を「delete」します。

f:id:yataiblue:20151027174525j:plain

次にVIewController内のコードを確認します。UITextFieldDelegateの次のメソッドから「mealNameLabel.text = textField.text」を消します。

func textFieldDidEndEditing(textField: UITextField) {
  mealNameLabel.text = textField.text
}

そしてPropertiesにある次の@IBOutletも消去します。

@IBOutlet weak var mealNameLabel: UILabel!

そして最後にViewController.swiftの名前をもう少し意味のある名前に変更します。このビューコントローラーの名前を変更する方法ですが2通りの方法があることを既に説明しています。

yataiblue.hatenablog.com

このチュートリアルでは2つ目の方法で変更しています。

  • プロジェクト・ナビゲータで「ViewController.swift」を一度クリックして選択します。それから「Return」キーを押すと名前が編集可能になるので「MealViewController.swift」に変更して、「Return」を押します。
  • MealViewController.swiftファイル内のクラス宣言にある「ViewController」を下記のように「MealVIewController」に変更します。コメント内のViewControllerもMealViewControllerに変更しておきます。
class MealViewController: UIViewController, 
        UITextFieldDelegate, UIImagePickerControllerDelegate, 
             UINavigationControllerDelegate {
  • Main.storyboadを開いてMealViewControllerのScene Dockを選択状態にしてからアイデンティティ・インスペクタを開きます。「Custom Class」の「CLass」から「MealViewController」を選びます。

これで変更できました。ランしてみると前回と同様にテーブルビューが表示されるはずです。まだMeal Sceineとの連携はできていません。今回の課題はここまでです。

次に進む→Swiftで遊ぼう! - 389 - Implement Navigation - Swiftで遊ぼう! on Hatena