UITableViewのDataSourceをprotocolで定義しておく


*追記: 更新版を書きました。->「[UITableViewのDataSourceをprotocolで定義しておく 2 - Qiita](http://qiita.com/morizotter/items/6785b5aefaa6ebe67e7c)」*

TableViewの中身を書くのが面倒だったので、protocolでうまい具合に制御して決まり切った書き方ができないかなぁと思いながら書いていたらそれっぽいのになったのでメモとして残しておきます。この方法がすごく良いかはまだわかりませんが、書くタイミングを失うことを恐れて今書きます。

## protocolで制約を設けておく

用意したprotocolはこの3つです。必要最低限のことを書いていますが、このやり方がうまくいくのならばもっと拡張しても良いと思います。

```swift
protocol TableViewRowType {
}

protocol TableViewSectionType {
    typealias Row: TableViewRowType
    var rows: [Row] { get }
}

protocol TableViewDataSourceType {
    typealias Section: TableViewSectionType
    var sections: [Section] { get }
}
```

## protocolに準拠した型を定義する

今回は設定画面のセルを想定します。ボタンが一つ付いたセルだったり、DisclosureIndicatorだったり、ログアウトのようなシンプルなセルだったり幾つかの種類があるとします。もちろんそれぞれタップなどをした時のアクションは異なります。

```swift
enum SettingsCellType {
    case Colors
    case License
    case InAppPurchase
    case Restore
    case Version
}

struct SettingsRow: TableViewRowType {
    let title: String?
    let subTitle: String?
    let cellType: SettingsCellType
    
    init(title: String?, subTitle: String?, cellType: SettingsCellType) {
        self.title = title
        self.subTitle = subTitle
        self.cellType = cellType
    }
}

struct SettingsSection<Row: TableViewRowType>: TableViewSectionType {
    let rows: [Row]
    let title: String?
    
    init(rows: [Row], title: String?) {
        self.rows = rows
        self.title = title
    }
}

struct SettingsDataSource<Section: TableViewSectionType>: TableViewDataSourceType {
    var sections: [Section]
    
    init(sections: [Section]) {
        self.sections = sections
    }
}
```

こんな形にしてみました。下から見ていくと、DataSourceは `TableViewDataSourceType` に準拠していています。 `sections` 配列が格納するのは `TableViewSectionType` に準拠した型です。ジェネリクスにせずTableViewSectionTypeのままでもいいのですが、利用するときにダウンキャストをするのが面倒なのでジェネリクスにしています。

Sectionも同様に `TableViewRowType` に準拠した型格納する `rows` を持っています。

SettigsRowはとりあえず、 `title` と `subtitle` と `cellType`を持たせました。 `cellType` は後ほどアクションやセルの見た目を分岐するために利用します。表示するセルの数だけあると考えれば良いと思います。

## UIViewControllerから利用する

`dataSource` に値を格納するところの書き方は人それぞれ好みは分かれそうですがこんな感じになりました。まずは、 `viewDidLoad` の周辺です。

*viewDidLoad*

```swift
class SettingsViewController: UITableViewController {

    var dataSource: SettingsDataSource<SettingsSection<SettingsRow>>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        dataSource = SettingsDataSource<SettingsSection<SettingsRow>>(sections: [
            SettingsSection<SettingsRow>(rows: [
                SettingsRow(title: "Colors", subTitle: nil, cellType: .Colors)
                ], title: "General"),
            SettingsSection<SettingsRow>(rows: [
                SettingsRow(title: "InAppPurchase", subTitle: nil, cellType: .InAppPurchase),
                SettingsRow(title: "Restore", subTitle: nil, cellType: .Restore)
                ], title: "Purchase"),
            SettingsSection<SettingsRow>(rows: [
                SettingsRow(title: "License", subTitle: nil, cellType: .License),
                SettingsRow(title: "Version", subTitle: nil, cellType: .Version)
                ], title: "Application")
            ])
    }
...
```

`dataSource` の宣言部分では、必要なジェネリクスの宣言もすることになります。次に、 `UITableViewDataSource` の部分です。

*UITableViewDataSource*

```swift
// MARK: UITableViewDataSource
extension SettingsViewController {
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return dataSource.sections.count
    }
    
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.sections[section].rows.count
    }
    
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let row = dataSource.sections[indexPath.section].rows[indexPath.row]
        switch row.cellType {
        case .Colors:
            let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
            cell.textLabel?.text = row.title
            cell.detailTextLabel?.text = row.subTitle
            cell.accessoryType = .DisclosureIndicator
            return cell
        case .InAppPurchase:
            // Return cell
        case .Restore:
            // Return cell
        case .License:
            // Return cell
        case .Version:
            // Return cell
        }
    }
}
```

UITableViewCellは複数の種類を分岐で使い分けできるようになっています。ただ、例としては全て分岐していますが、必要に応じてまとめて処理をすることになると思います。次は `UITableViewDelegate` 周りです。

*UITableViewDelegate*

```swift
// MARK: UITableViewDelegate
extension SettingsViewController {
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let row = dataSource.sections[indexPath.section].rows[indexPath.row]
        switch row.cellType {
        case .Colors:
            // Do something
        case .InAppPurchase:
            // Do something
        case .Restore:
            // Do something
        case .License:
            // Do something
        case .Version:
            // Do something
        }
    }
    
    override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return dataSource.sections[section].title
    }
}
```

セルによってタップの処理を変えています。自由な値を追加できる例として追加していた `title` をSectionヘッダーに表示してみました。

## まとめ

と、こういう形でprotocolに適合させた形で型を書くのは面倒ですが、一度書いてしまえば扱いやすい形になりました。ただ、全てこれでやるというのはあまり考えていなくて、そもそも既存のモデルをTableに表示するなどするときに、 `TableViewRowType` に準拠させるかというと微妙なところもあると思っています。実際に使えるのは今回の設定のようなその画面に必要な要素をその場で作るという時くらいかもしれません。

また、あえてprotocolに準拠させるのはコスト的に無駄かもしれません。同じ構造の型を直接作れば良い話なので。さらに、あえて型がわかっているならジェネリクスにしなくても良いかもしれません。などなど突っ込みどころ満載ですが、考えた記録として残しておきます...。