【Swift5】Swift入門 ~ UITableViewを使ってみる ~

2020.01.15

どうも、2020年初登場のむつたくです。
今年も技術情報をたくさんアウトプットしていく予定ですので、よろしくお願いします。

 

さて、これからですがタイトルにあるようにSwiftの入門的な記事を書いていこうと思ってます。一発目の今回は「UITableView」を紹介します。
幅広い用途で使用されるコントローラなので、覚えておいて損はないと思います。

 

今回やること

  • UITableViewについて
  • データの表示
  • セクションを使う
  • タップイベントの発火(コンソールに出力)

 

開発環境

  • macOS Catalina(10.15.1)
  • Xcode 11.3
  • Swift 5.1
  • Simulater iPhone 11Pro
  • iOS 13.3

実機で確認できる場合は、実機でも動作確認をオススメします。意外と動きが違う!とか表示が崩れてる!とか色々聞きますので。ちなみに自分はAndroidです。シュミレータ必須です。

 

また、Xcodeでは、ProjectのSingleViewAppで作成します。

 

UITableView について

UITableViewは、テキストやイメージを行単位で表示することが出来ます。こんなコントローラですね。

 

まっさらな状態ですが、線で区切られていて、それが行として表示されます。そこにテキストを格納したり、様々なイベントを発生させたりと優れもののコントローラです。

このコントローラは、色々なアプリの設定画面とかのリストで使われているような気がします。
1行1行をそれぞれセルと言います。それを構成するのが、「UITableViewCell」です。冒頭で記述したようにテキストを表示したり、カスタマイズしてイメージを表示したりと、セルのレイアウトを変更します。この辺は別の機会に説明できればと思ってます。

 

その他に、「Delegate」と「DataSource」があります。
Delegateは、テーブル操作のイベント処理が、DataSourceはテーブルのセクションやセルの値を設定するためのメソッドが定義されてます。

 

では、早速UITableViewを使っていきましょう!

 

データの表示

TableViewの画面配置

雛形を作成するにあたり、とりあえずサクサクっとTableViewを配置して、画面いっぱい広げましょう。

 

次に「TableViewCell」をTableViewに配置します。

 

そうしましたら、TableViewCellのIdentifierを設定します。ここでは「NameCell」とします(識別子なので、識別できるのであれば何でも構いません)。

 

次にコードです。15行以降を追記してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ViewController.swift
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
}

// セクションやセル値を管理する
extension ViewController: UITableViewDataSource {

    // セクション毎の行数を返す
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 0
    }

    // 各行に表示するセルを返す
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return UITableViewCell()
    }
}

// テーブルのイベントを管理する
extension ViewController: UITableViewDelegate {

}

 

これでTableViewの雛形は完成しました。DataSourceの各関数はOverloadされているので注意してください。2番目の引数がそれぞれ変わっているので、それが目印です。
numberOfRowsInSection」では、TableViewのセクション毎の行数を返すようにDataSourceにリターンしてます。
cellForRowAt」では、TableViewの特定の場所(セル)に挿入するセルをDataSourceにリターンしてます。

 

さて、ここからTableViewへ表示するデータを流し込む処理を記載していきましょう。

 

データの準備

まずは、Main.storyboardのTableViewとViewController.swiftを連携させます。TableViewをViewController.swiftへcontrolキーを押しながらドラッグアンドドロップしましょう(トラックパッドを二本指でクリックしドラッグアンドドロップでも出来ます)。ここではNameを「tableView」としてます。

 

連携させると選択した行に以下のコードが追加されたと思います。

1
    @IBOutlet weak var tableView: UITableView!

 

データは何でも良いですが、配列型で定義します(対象:行6)。
ついでにTableViewの初期設定も済まします(対象:行12 ~ 17)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    // ↓追加 <テーブル表示データ>
    let tableData = ["水戸駅", "偕楽園駅", "日立駅", "土浦駅", "つくば駅", "研究学園駅"]

    override func viewDidLoad() {
        super.viewDidLoad()

        // ↓追加 <tableview初期設定>
        tableView.frame = view.frame
        tableView.dataSource = self
        tableView.delegate = self
        tableView.tableFooterView = UIView(frame: .zero)
    }
}

 

行12では、テーブルを画面サイズいっぱいまで広げてます。StoryBoardで行ったので意味はありませんが…。
行13、14で画面とTableViewのデータソースとデリゲートを紐づけてます。これをしないとTableViewを制御できないので忘れずに記載してください。
行15でデータが無いセルを非表示にする制御を追加してます。
初期設定はこんな感じで大丈夫でしょう。

 

DataSourceの修正

まずは、「セクション毎の行数」を設定します。初期状態のままだと、0行で表示できないので、データ件数分表示したいので、以下のように変更します。

1
2
3
4
// セクション毎の行数を返す
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return tableData.count
}

 

次は、セルに表示するテキストの編集です。現状だと空白なので、「tableData」を表示するように処理を修正します。

1
2
3
4
5
6
7
// 各行に表示するセルを返す
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // StoryBoradで定義したTableViewCellを取得する
    let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "NameCell", for: indexPath)
    cell.textLabel?.text = tableData[indexPath.row]
    return cell
}

 

この状態で、実行すると定義してあげたデータがTableViewに表示されたかと思います。下のイメージのように表示されてればOKです!

 

セクションを使う

セクションを使うとこんな感じにグループ化出来ます。

 

セクションデータの準備

まずはデータですが、「tableData」を修正するのと、新たに定数を定義します。ここでは「sectionData」とします。

1
2
3
4
5
6
7
    // ↓追加 <セクション表示データ>
    let sectionData = ["常磐線", "つくばエクスプレス"]
    // ↓修正 <テーブル表示データ>
    let tableData = [
        ["水戸駅", "偕楽園駅", "日立駅", "土浦駅"],
        ["つくば駅", "研究学園駅"]
    ]

 

まず、「sectionData」ですが、グループ化したいモノ(名前)を定義します。次に「tableData」ですが、配列から多次元配列へ変更しました。「sectionData」の要素が2つなので、それに合わせて多次元配列を定義します。

 

DataSourceの修正

セクションを使用するにあたり、こちらも新たにメソッドを追加する部分と、変更する部分があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// セクションやセル値を管理する
extension ViewController: UITableViewDataSource {

    // セクション毎の行数を返す
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // ↓修正
        return tableData[section].count
        // return tableData.count
    }

    // ↓追加 セクション数を返す
    func numberOfSections(in tableView: UITableView) -> Int {
        return sectionData.count
    }

    // ↓追加 セクションヘッダの高さ
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 40
    }

    // 各行に表示するセルを返す
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // StoryBoradで定義したTableViewCellを取得する
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "NameCell", for: indexPath)
        // ↓修正
        cell.textLabel?.text = tableData[indexPath.section][indexPath.row]
        // cell.textLabel?.text = tableData[indexPath.row]
        return cell
    }
}

// テーブルのイベントを管理する
extension ViewController: UITableViewDelegate {

    // ↓追加 セクションヘッダを返す
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return sectionData[section]
    }
}

 

行7で、各次元の要素数を良い感じに返してくれます。
行12~14で、セクション数を返します。ここでは「sestionData」の要素が2つなので、2が返されます。
行17~19で、全セクションの高さを設定します。初期値だと文字サイズギリギリの高さになるので、ここではセルの高さに合わせ「40」に設定してます。
行28で各セルのテキストを設定してますが、各次元毎に設定しないといけないので、「indexPath.section」を使用します。
行40~42で、各セクションのヘッダテキストを設定します。
これで完了です。実行すれば、冒頭のイメージになると思います。

 

ちなみに、「titleForHeaderInSection」は、TableViewの指定されたセクションのヘッダーのタイトルをDataSourceにリターンしてます。
heightForHeaderInSection」は、特定のセクションのヘッダーに使用する高さをDelegateにリターンしてます。ここだけDelegateなので、注意してください(おそらく、DataSourceに記載しても動作するとは思いますが、正しいのはDelegateに記載です)。

 

タップイベントの発火

次はタップされたらタップイベントを発火させる、と言うことをやっていきます。

 

行情報をコンソールに出力

イベントを検知するにはDelegateを使用します。タップももれなくDelegateです。以下の感じになります。

1
2
3
4
5
6
7
8
// テーブルのイベントを管理する
extension ViewController: UITableViewDelegate {

    // ↓追加 セルタップ時のイベント
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print(indexPath)
    }
}

 

この状態で実行するとコンソールに「[0, 0]」とか「[1, 1]」とか出力されると思います。
ちなみにセクションが1つの場合だと、「indexPath」で行番号が取得できます。2つ以上の場合はコンソールに出力された形式になりますが、「indexPath.section」でセクション番号と「indexPath.row」でセクション内の行番号をそれぞれ取得することができます。

1
2
3
4
5
6
7
8
9
10
// テーブルのイベントを管理する
extension ViewController: UITableViewDelegate {

    // セルタップ時のイベント
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print(indexPath.section)
        print(indexPath.row)
        print(tableData[indexPath.section][indexPath.row])
    }
}

 

引数で宣言されている「didSelectRowAt」は、現在選択した行をDelegateに通知してます。これで行番号を取得して、配列を参照することで該当行の情報が取得できると言う仕組みです。

 

まとめ

TableViewの基本的な使い方を紹介しました。いかがでしたでしょうか?
TableViewのセクションやセルの情報を管理するのが「DataSource」、イベントを管理するのが「Delegate」になります。
セクションを使用しない場合は、一次元配列を使用し、行番号を取得するにはindexPathを使う。セクションを使用する場合は多次元配列を使用し、セクション番号を取得するには、indexPath.sectionを、セクション毎の行番号を取得するには、indexPath.rowを使用します。ここを頭の片隅にでも置いておければ今後役に立つかもしれません…。

 

次回は、今回のに「Navigation Controller」を加えて画面遷移を実現していこうと思います。
それではまたの機会に!


Top