RxSwiftのUITableViewとのバインディングまとめ

RxSwiftを使ってiOSのUIとデータをバインドするときに、ちょっと躓きやすいのがUITableViewとのバインディングです。少なくとも自分は躓きました。
ということで、調べたことをまとめておきます。

例として `Item` というクラスの配列が流れてくる `items` というObservableを、UITableView（`tableView`）とバインドすることを考えます。

パターンとしては、次の4つがあります。

## パターン 1

```swift
let items: Observable<[Item]> = ...

items
    .bind(to: tableView.rx.items(cellIdentifier: "Cell")) { row, element, cell in
        // row: Int …… アイテムのインデックス
        // element: Item …… アイテムのインスタンス
        // cell: UITableViewCell …… セルのインスタンス
        
        // ここでセルの中身を設定する
        cell.textLabel?.text = element.name
    }
    .disposed(by: disposeBag)
```

最もシンプルなのがこのパターンです。

- `rx.items` の第1引数にidentifierを渡す。
- クロージャーの中で、セルに値をセットする。


## パターン 2

```swift
let items: Observable<[Item]> = ...

items
    .bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: MyTableViewCell.self)) { row, element, cell in
        // row: Int …… アイテムのインデックス
        // element: Item …… アイテムのインスタンス
        // cell: MyTableViewCell …… セルのインスタンス
        
        // ここでセルの中身を設定する
        cell.nameLabel.text = element.name
        cell.ageLabel.text = "\(element.age)"
    }
    .disposed(by: disposeBag)
```

パターン 1ではクロージャーに渡ってくる `cell` の型が `UITableViewCell` でしたが、カスタムセルを定義しているような場合は、その型を指定することができます。

もちろん、あらかじめCell Identifierとカスタムセルのクラスの結びつけをStoryboardで設定しておくか、 `register(_:forCellReuseIdentifier:)` で登録しておく必要があります。

## パターン 3

```swift
let items: Observable<[Item]> = ...

items
    .bind(to: tableView.rx.items) { tableView, row, element in
        // tableView: UITableView …… テーブルビュー
        // row: Int …… アイテムのインデックス
        // element: Item …… アイテムのインスタンス

        // ここでセルを作って、中身を設定する
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
        cell.textLabel?.text = element.name
        return cell
    }
    .disposed(by: disposeBag)
```

パターン 1、2ではidentifierを引数に指定していましたが、パターン 3ではセルを作るところがクロージャーの中に入ります。
データによって、複数のカスタムセルをそれぞれのidentifierで使い分けているような場合はこちらを使うことになります。
このクロージャーの中でやることは、 `UITableViewDataSource` の `tableView(_:cellForRowAt:)` を実装するときとほぼ同じと考えていいと思います。


## パターン 4

```swift
let items: Observable<[Item]> = ...

let dataSource = ...
items
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)
```

パターン 1〜3 と違って、データソースを自分で指定する方法です。
これが一番なんでもできるパターンです。
例えば、 `UITableViewDataSource` の `tableView(_:canEditRowAt:)` を実装しなきゃいけないとか、セクションのヘッダーやフッターが必要といった、個別のデータソースの実装が必要な場合はこちらを使います。

ただし、渡す `dataSource` の型は`RxTableViewDataSourceType & UITableViewDataSource` なので、 単純に `UITableViewDataSource` に準拠するだけでなく、 `RxTableViewDataSourceType` にも準拠する必要があります。

### データソースの例

ひとまず、次に挙げるだけの実装を行えば、上記のようにバインドして使えるものになります。

```swift
class MyDataSource: NSObject, UITableViewDataSource, RxTableViewDataSourceType {
    typealias Element = [Item]
    var _itemModels: [Item] = []
    
    // MARK: UITableViewDataSource
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return _itemModels.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        let element = _itemModels[indexPath.row]
        cell.textLabel?.text = element.name
        return cell
    }
    
    // MARK: RxTableViewDataSourceType
    
    func tableView(_ tableView: UITableView, observedEvent: Event<Element>) {
        Binder(self) { dataSource, element in
            dataSource._itemModels = element
            tableView.reloadData()
        }
        .on(observedEvent)
    }
}
```

`RxTableViewDataSourceType` が必要とするメソッド `tableView(_:observedEvent:)` では、Observerに次のデータがやってきたときの処理を書くことになります。
この例では、ここで `_itemModels` を更新して `reloadData` を呼び出すことでUITableViewを更新しています。
ここを工夫して `beginUpdates` 、 `endUpdates` 、 `insertRows(at:with:)` 、 `deleteRows(at:with:)` ……などを使うようにすれば、アニメーションを伴う更新も可能です。

### セル選択時の `modelSelected` に対応させる

`rx.modelSelected` を使いたければ、データソースは `SectionedViewDataSourceType` にも準拠させる必要があります。
次のように実装しておけばいいでしょう。

```swift
class MyDataSource: NSObject, UITableViewDataSource, RxTableViewDataSourceType, SectionedViewDataSourceType {

    ... // 上記の実装

    // MARK: SectionedViewDataSourceType

    func model(at indexPath: IndexPath) throws -> Any {
        return _itemModels[indexPath.row]
    }
}
```

### おまけ

下記のRxSwiftCommunity/RxDataSourcesで、パターン 4のデータソースとして使える便利なものが公開されています。そのまま使うこともできるし、自分でデータソースを作るときの参考にもなると思います。

https://github.com/RxSwiftCommunity/RxDataSources