未分類

【Swift入門】TableViewを作成しよう

こんばんは。
今回はアプリ作成に欠かせないTableViewを作成する手順をまとめていきます。

前回GitHubにiOSアプリの新規リポジトリを作成するでGitHubのリポジトリを作成しておきながら中身を全くいじってないプロジェクトファイルがあるのでここをついにいじっていきます。

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

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

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

  • Main.storyboard
  • ViewController.swift

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

Storyboardの設定

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

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

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

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

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

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

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

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

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

まずIBOutletを設定します。下の用に設定します。

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

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

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

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

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

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

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

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

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

それでは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
      }
  }

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

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

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

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

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

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

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

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

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

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

あと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に表示させるのをやりたいと思います。

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