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


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

##  環境
- mac OS Sierra
- Xcode 9.2
- Swift 4.0

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

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

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

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

```

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

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

```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で返しています（ちなみにこのように同じ定数名で行うオプショナルバインディングをシャドーイングという）。

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

```swift:ViewController.swift
var animals: [String]?

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

    return animals.count ?? 0
}
```

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

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

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

```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つだけ返す
        switch isFlag{
        case true:
            return animals.count
        case false:
            return 1
        }
    } else {
        return 0
    }
}
```

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

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

```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にセルを登録しています。

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

```

```swift:CustomCell
    static let cellIdentifier = "CustomCell"
```

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

## セルの情報

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

```swift: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で情報取得してます。なかなか初初しさが醸し出されてます。
上記のコードの問題は、配列要素がちゃんと整合性がとれているか確認する必要があります。その分コストや安定性が損なわれます。

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

```swift: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での複数プロパティをまとめる書き方です。

```swift: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は最近でた新しめのものですが、初めてでも結構簡単に使えるのでおすすめです。
結構最近まで次のような感じで書いてました。

```swift:Animal.swift
class Animal: Codable {
    let type: String
    let emoji: String
}
```



```swift: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に扱うモデルをもつプロパティを用意して、セットされたらラベルにデータを設定しています。

```swift:CustomCell
var animal: Animal! {
    didSet {
        self.celltitleLabel?text    = animal.type
        self.animalEmojiLabel?.text = animal.emoji
    }
}
```

```swift: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で分けて書いてることが多いです。
可読性のため、分けたほうがいいかなと個人的にも思ってます。

```swift:ViewController.swift

extension ViewController: UITableViewDataSource {

}

extension ViewController: UITableViewDelegate {

}
```

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