Swift

【Swift入門】初心者でもわかるTableViewを実装しよう

こんばんは。
今回はアプリ作成に欠かせないのに加えて、特有な設定が多くて初心者が挫折しやすいTableViewを作成する手順をまとめていきます。
画面キャプチャを多く使用して、できる限りわかりやすいように説明していきます。

この記事はこちらのswift入門のアプリ実践開発編のPart1です

〜初心者でもわかるswift入門〜基本文法からiOSアプリを開発するまでこのシリーズではプログラミング初心者の人や、swiftを初めて触れる人、やってみたもののよくわからないという人向けに、swiftの基本文法から実際に動くiOSアプリをXcodeで開発するところまでを解説します。...

今回扱うファイル

こちらの記事でプロジェクトファイルを作成しまだいじっていない状態でGitHubのリポジトリを作成しました。
今回はこちらのプロジェクトファイルをいじっていきます。

【Git】GitHubにiOSアプリの新規リポジトリを作成するこんにちは。今回はswift学習用にプロジェクトを作成して、githubにリポジトリを作成して管理できるようにするまでを説明します。 ...

まず前回プロジェクトファイルを作成した際に、SingleViewApplicationで作成しましたので中身のファイルは、

  • AppDelegate.swift
  • ViewController.swift
  • Main.storyboard
  • Assets.xcassets
  • LaunchScreen.storyboard
  • Info.plist

となっていると思いますが、今回はこれらから、

  • ViewController.swift
  • Main.storyboard

こちらの2つをいじってTableViewを表示できるようにしようと思います。
なお、storyboardviewControllerは1:1で対応するように作成するのが一般的ですが、デフォルトで作成されているこの2つは既に対応するように設定されています。

Storyboardの設定

StoryBoardにTableViewを設置する

まず、Main.storyboardを開き、右下の検索できる部分に「table」と入力したら候補に「Table View」というのがでてくるのでそれをこのようにドラッグ&ドロップします。

TableViewを画面いっぱいに設定する

次にこのままだとTableViewをただのっけただけになるので、tableViewが画面いっぱいになるように設定します。

上の画像の赤く囲ったところを押してこのように設定することができます。オートレイアウトの設定をするときに使うものです。
すると下の画像のようになります。オレンジになっているのは設定は特に問題なく設定できていますが、まだ画面上で反映されてないので赤く囲った部分を押すことで更新することができます。

下の画像のようになりました。

デフォルトでは左右にマージンが設定されているのでこれを画面横端いっぱいになるように設定します。

まずは左側の縦の青い線を一回クリックするとフォーカスを当てることができて、右側のコマみたいなボタンを押して画像のようにデフォルトでは「Relative to margin」という項目にチェックが入っているので外します。

右側も同様に行いましょう。左側のときは「Second Item」、右側のときは「First Item」のところで違う場所になっているので注意です。

設定が完了するとこのようになり、TableViewが画面いっぱいに設定することができました。

ViewControllerの設定

TableViewとViewControllerのヒモづけ

この状態では、ただStoryBoardに貼り付けただけの状態なので、次は中身を描画するための設定をしていきます。
それにあたって、ViewControllerTableViewの中身をどうするかの設定をできるように設定する必要があります。なのでこのTableViewViewControllerのヒモ付を行います。

まずIBOutletを設定します。下の用に設定します。
これはTableViewの上にカーソルを合わせて、commandキーを押しながらドラッグします。

DelegateとDatasourceの設定

次にDelegateDatasourceを設定します。下のように設定します。DelegateDatasourceに関しては後ほど説明します。

設定が完了すると下の画像のように、3つのコネクションがある状態になっていれば大丈夫です。よくうまくいかなくて何回も作り直したりしていると、部品は消してもコネクションだけ残り続けて、ない部品を参照してアプリがクラッシュするっていうことに陥ることが多いのでここを確認するようにしましょう。

以上をすることで設定したViewControllerTableViewをいじれるようになりました。

TableViewCellの作成

xibファイルを作成する

つぎにセルのファイルを作成していきます。コードで書いていく方法もありますが僕らのチームではxibファイルを作成して管理しているのでその方法を説明していきます。

コマンドNで新規ファイルを作成します。「Cocoa Touch Class」を選択します。

今回はUITableViewCellを作成して、「Alse create XIB file」にチェックをするとXibファイルも作成してくれてヒモ付もされた状態になっています。
StoryBoardと同様にUITableViewCellもレイアウトのファイルとコードを書くファイルが分かれていてヒモ付をしてセットで扱います。

Identifierの設定

ヒモ付は完了していますが、Identifierを設定しないといけません。基本的にファイル名と同じ名前で大丈夫です。あとでTableViewにセルの登録をする際に必要になってくるものです。ここの設定が間違っていて闇に陥るケースが多いのでつづりとかはなるべくコピペしたりして間違えないようにしましょう。

Labelを設置する

とりあえずLabelをセルの真ん中に設定しました。
縦軸横軸に対して中心に設定をこの様にすることができます。

ひとまずこんなかんじになりました。

ViewControllerの書き換え

TableViewが表示されるようにViewControllerを書き換える

それではViewControllerを書き換えて実際にTableViewを表示させてみましょう。



import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "TableViewCell")
    }
}

extension ViewController: UITableViewDataSource {

    // セクションの個数を返す
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    // セクションごとにセルの個数を返す
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    // セルの中身を返す
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: TableViewCell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell
        return cell
    }

    // セルの高さを返す
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100
    }
}

extension ViewController: UITableViewDelegate {

    // セルがタップされたときの処理を書く
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        return
    }

    // スクロールしたときの処理を書く
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        return
    }
}

まずこのように書きました。
そうするととりあえずこのように表示されました。

とりあえず動いてそうなので一安心です。

delegateとは

コードを1つ1つ説明していこうと思いますがその前にdelegateの説明をしていきます。
delegateを和訳すると「移譲」という意味で、swift的には実装を託すという意味合いになります。

TableViewでいうと、今回の実装方法ではTableViewの移譲先をViewControllerに設定しました。

TableViewでは、あとは表示させるセルの個数とか中身とかタップしたらどうするかだけ教えてくれれば、その通りに実現するように実装したからあとはその実装だけよろしく、というようなかんじでViewControllerに託しています。

すき家でサイズとトッピングだけ指定してくれれば牛丼がつくられるみたいなかんじですね。

先程DelegateDataSourceの2つを設定しました。それぞれで使い道が異なっています。
DataSourceではセルの個数や中身、Delegateではイベント発生時の処理を移譲するようになっています。
それぞれ例えばDataSourceでは表示させるセルの個数を設定するメソッドが用意されていたり、Delegateではタップされたときの処理を書くメソッドが用意されています。

先程の例ではよく使うメソッドをそれが何をするものなのかを書いていきました。この他にも細かいものはたくさんあるので興味があったら調べてみてほしいです。

他の言語をやってる人で、メソッド名が同じものが多かったり、これらのメソッドの呼び出し元が一切かかれてないじゃないことに違和感がある人もあるとは思いますが、引数の中身がそれぞれ違っていて、引数や返り値で何をやっているのかおおよそ検討がつくのではないのかなと思います。

あと先程移譲の話をしたと思いますが、ViewControllerで定義したメソッドたちはTableViewに呼ぶ処理が書かれていて、それぞれ適切なタイミングでViewControllerに設定されたメソッドを呼ぶ実装がTableViewになされています。

IndexPathとは

あとIndexPathについて説明します。
cellForRowAtメソッドdidSelectRowAtメソッドではindexPathがメソッド内で使えます。
indexPathというのは対象のセルのセクションとインデックスを持っています。
didSelectRowAtメソッドではセルがタップされたときに呼ばれるメソッドで、タップされたセルのセクションとインデックスがindexPathに入っています。
cellForRowAtは1つ1つのセルを描画する際に呼ばれて、表示しようとしてるセルの個数分だけ個別に呼ばれます。作られようとしてるセルに対してそのセルのセクションとインデックスがindexPathに入っています。

少し書き換えてみる

先程の例では表示はできましたが、すごくしょぼい結果なのでindexPathを踏まえて少し書き換えてみます。

まず、Cellをこのように書き換えました。


import UIKit

class TableViewCell: UITableViewCell {

    @IBOutlet weak var label: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

    func bindData(text: String) {
        label.text = text
    }
    
}

これをすることで、Labelの文字をbindDataに渡した文字に書き換えることができます。
ViewControllerはこのように書き換えました。


import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "TableViewCell")
    }
}

extension ViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        return 2
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: TableViewCell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell
        cell.bindData(text: "section: \(indexPath.section) index: \(indexPath.row)")
        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 50
    }
}

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("section: \(indexPath.section) index: \(indexPath.row)")
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        return
    }
}

このように書き換えるとどうなるかをイメージしてみてほしいです。
下のようになりました。

一番上のセルをタップしたらちゃんとプリントされています。

今回はTableViewが簡単に扱えるようになりました。
次回はapiから表示するためのデータを取得しそれをTableViewに表示させるのをやりたいと思います。

今回のコードはこちらでも確認できるのでよかったらご確認ください。