Help us understand the problem. What is going on with this article?

初心者から中級者になるためのTableview tips (Swift)

More than 1 year has passed since last update.

目的

 独学でSwift学び始めて1年、実務で使い始めて数ヶ月たちました。最初に書いてたコードを見てみると、こうしたら良かったのにな〜みたいなところが多々あるので、昔の自分と同じようなSwift学びたての人が改善できそうなポイントについて記述します。
 特に使い所が多いTableviewについて、今回はまとめていきます。TableViewとCollectionViewは似ているところも多いので、今回紹介するtipsはCollectionViewにもほとんど使えます。

環境

  • mac OS Sierra
  • Xcode 9.2
  • Swift 4.0

セル、もしくはセクション数のカウント

基本的なとこ

 これは多くの人が当たり前のようにやってると思いますが、基本的に配列のcountメソッドを使って次のように記述することがほとんどだと思います。

ViewController.swift
let animals = ["🐶","🐱","🐭","🐷","🐴"]

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

要素の配列がオプショナルなとき

次にこの配列がAPIからのレスポンスを入れるなどしてオプショナルの場合どのように書きますか?
Swift習いたては自分は次のように書いてました。

ViewController.swift
let animals: [String]?

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if let animals = animals {
        return animals.count
    }
    return 0
}

オプショナルバインディングしてanimalsが存在しているときに配列の数を返しています。nilであれば0で返しています(ちなみにこのように同じ定数名で行うオプショナルバインディングをシャドーイングという)。

現時点の私は次のような形で書きます。

ViewController.swift
var animals: [String]?

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

    return animals.count ?? 0
}

1行で書けるので、こちらのほうが良いですね

条件によって表示数が変わる時

それでは次に、ある変数がtrueかfalseによって表示するセルの数がかわる場合はどのように書くでしょうか。例えば、課金しているユーザーとそうでないユーザとで分ける場合です。

ViewController.swift
var isFlag = false
var animals: [String]?

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

    if let animals = animals {
        // Flag->true:配列の数を返す。
        // Flag->false:1つだけ返す
        switch isFlag{
        case true:
            return animals.count
        case false:
            return 1
        }
    } else {
        return 0
    }
}

Switchを使って処理を分けてます。
ちなみにSwiftのSwitch使ってから他の言語でSwitch使おうとするとbreakを都度書かなければならないので、Swift最高〜ってなります。

話がそれましたが、上のようなコードであれば、今だとこのように書きます。

ViewController.swift
var isFlag = false
var animals: [String]?

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

    if let animals = animals {
        // Flag->true:配列の数を返す。
        // Flag->false:1つだけ返す
        return isFlag ? animals.count : 1
    } else {
        return 0
    }
}

書き換えた所はswitchのところを三項演算子にしました。
独学のときは三項演算子使ってなかったんですが、これ使えるとif分の処理が結構書き換えれます。
これも1行で書けますし、他の言語でも使うので、是非マスターしましょう。

カスタムセル

作り方

Swift開発序盤のほうから標準のUITableViewCellでは表現できない自作カスタムセルを作る必要が出てくると思います。
自分の場合ですが、最初は
カスタムセルクラス作成→StoryboardのTableViewのセルにカスタムセルクラスを設定

という感じで作ってました。
しかし、小規模な個人開発以外はxibで作るのがベストだと今は考えてます。
xibで作るメリットは
- Storyboardの変更が生じない→Storyboardでのコンフリクトを防ぎやすい
- 個別のファイルなので、管理が楽
- Identifierをコードで一元管理(後述)

という感じです。
Swift習いたての個人開発だとxib恐い、ってなってたんですが、何回か使っていけば慣れるし、今後メインでiOS開発するならば避けては通れないので、早めに使っといたほうが良さげです。

CellIdentifier

カスタムセルを使う場合には、セルにIDを登録する必要があります。
storyboardでカスタムセルを設定した場合には、IDもStoryboardに登録することができます。しかし、呼び出すときに
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell") as! CustomTableViewCell
のように、直で文字打つともし間違ったときにクラッシュします。
他にもperformSegueでもIDを間違うとクラッシュするので、安定したアプリ開発の際には、ここらへんは注意すべきです。

今ではカスタムセルクラスにIdentifierプロパティを作って、それを参照してtableviewにセルを登録しています。

ViewController.swift
    // viewdidloadとかで
    tableView.register(nib, forCellReuseIdentifier: customCell.cellIdentifier )

CustomCell
    static let cellIdentifier = "CustomCell"

ここを変数作らずに、クラス名を文字列にするExtension作って、セル登録に使う人もいます。

セルの情報

扱うデータが静的な場合

ここが今回一番伝えたいポイントです。
やってる人いないかもしれませんが、最初のころの私は次のようなコードを書いてました。

ViewController.swift
    let titleList = ["Dog","Cat","Rat","Pig","Horse"]
    let animals = ["🐶","🐱","🐭","🐷","🐴"]

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = myTableView.dequeueReusableCell(withIdentifier: "cell") as! CustomCell
        cell.titleLabel?.text = titleList[indexPath.row]
        cell.animalEmojiLabel?.text = animals[indexPath.row]

        return cell
    }

セルに表示する各配列要素にindexPath.rowで情報取得してます。なかなか初初しさが醸し出されてます。
上記のコードの問題は、配列要素がちゃんと整合性がとれているか確認する必要があります。その分コストや安定性が損なわれます。

今だと↓のようになります。

ViewController.swift
    enum AnimalType: Int {
        case dog
        case cat
        case rat
        case pig
        case horse

        var title: String {
            switch self {
                case .dog:
                    return "🐶"
                case .cat:
                    return "🐱"
                case .rat:
                    return "🐭"
                case .pig:
                    return "🐷"
                case .horse:
                    return "🐴"
            }
        }

        var icon: String {
            switch self {
                case .dog:
                    return "Dog"
                case .cat:
                    return "Cat"
                case .rat:
                    return "Rat"
                case .pig:
                    return "Pig"
                case .horse:
                    return "Horse"
            }
        }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = myTableView.dequeueReusableCell(withIdentifier: CustomCell.cellIdentifier) as! CustomCell
        let animal = AnimalType(row: indexPath.row)
        cell.titleLabel.text        = animal?.title
        cell.animalEmojiLabel?.text = animal?.icon

        return cell
    }

表示する内容をEnumで管理しています。
行数は多くなりますが、この分Swiftの恩恵であるSwitchの網羅性を享受できます。
もしSwitch Caseを網羅できてない場合は、errorがでるので、漏れが生じません。
このように予め表示するデータが決まっている場合は、Enumで処理するときれいに書けることが多いです。

また、最近発見したのは、タプルをつかってEnumでの複数プロパティをまとめる書き方です。

ViewController.swift
    enum AnimalType: Int {
        case dog
        case cat
        case rat
        case pig
        case horse

        var prop: (title: String, icon: String) {
            switch self {
                case .dog:
                    return (title: "Dog", icon: "🐶")
                case .cat:
                    return (title: "Cat", icon: "🐱")
                case .rat:
                    return (title: "Rat", icon: "🐭")
                case .pig:
                    return (title: "Pig", icon: "🐷")
                case .horse:
                    return (title: "Horse", icon: "🐴")
            }
        }
    }

これであれば、さらに扱うプロパティが増えても大きく記述量が増えることはないですし、可読性も上がります。
タプル最高!!

APIなどで表示するデータが変則的なとき

めちゃめちゃ便利なCodableを使って次のようなモデルにAPIのレスポンスをマッピングし、それを表示するというケースを考えます。Codableは最近でた新しめのものですが、初めてでも結構簡単に使えるのでおすすめです。
結構最近まで次のような感じで書いてました。

Animal.swift
class Animal: Codable {
    let type: String
    let emoji: String
}
ViewController.swift
    let animalList: [Animal]?

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = myTableView.dequeueReusableCell(withIdentifier: "cell") as! CustomCell
        guard let animal = animalList?[indexPath.row] else { return cell }
        cell.titleLabel?.text = animal.type
        cell.animalEmojiLabel?.text = animal.emoji

        return cell
    }

この書き方でも問題ないですが、他の場所でも同じような処理が合ったりした場合や反映する情報が多くなってくると、その分コードが増えていきます。

現在では、次のようにCellに扱うモデルをもつプロパティを用意して、セットされたらラベルにデータを設定しています。

CustomCell
var animal: Animal! {
    didSet {
        self.celltitleLabel?text    = animal.type
        self.animalEmojiLabel?.text = animal.emoji
    }
}
ViewController.swift
    let animalList: [Animal]?

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = myTableView.dequeueReusableCell(withIdentifier: "cell") as! CustomCell
        guard let animal = animalList?[indexPath.row] else { return cell }
        cell.animal = animal

        return cell
    }

このようにすれば、セルに該当のクラスを渡すだけなので、ViewControllerの記述が簡潔になります。

その他

個人の好みかもしれませんが、周りで見てる感じだとTableViewのProtocol(UITableViewDataSource, UITableViewDelegate)はViewControllerにExtensionで分けて書いてることが多いです。
可読性のため、分けたほうがいいかなと個人的にも思ってます。

ViewController.swift
extension ViewController: UITableViewDataSource {

}

extension ViewController: UITableViewDelegate {

}

まとめ

今でも開発の中で新しい発見をしていて、そのたび「Swift最高〜」ってなってますので、現時点の書き方もまだまだ改善する部分があると思うので、他のやり方などあったらコメントください〜

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした