RxSwiftでTableViewをハックする🏄

# RxSwiftでTableViewをハックする🏄

#### RxSwiftでTableViewに配列を表示する２つの方法についてまとめてみました。

## Observableの配列をTableViewにBindする方法

#### これが多分最も一般的な方法

- ViewModelに定義されている`Observable`の配列を`ViewController`の`TableView`に`Bind`する方法

```swift
class ViewModel {

    var items: Observable<Item> {
        return Observable.of(Item.dummyData)
    }
}
```

```swift
class ViewController: UITableViewController {

    private let viewModel = ViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel.items
            .bindTo(tableView.rx.items(cellIdentifier: "Cell", cellType: ItemListViewCell.self)) { index, item, cell in
                cell.item = item
            }
            .addDisposableTo(disposeBag)
    }
}
```

## TableViewのSectionを分けたりHeaderViewを設定したりする方法

### これがあまり使われていない方法

#### Step 1

- ViewModelを作成する

```swift
class ViewModel {

    var elements: Observable<Item> {
        return Observable.of(Item.dummyData)
    }
}
```

#### Step 2

- `DataSource`を`ViewController`にネストで作成する。

##### この`DataSource`は`RxTableViewDataSourceType`に準拠する必要がある。


```swift
protocol RxTableViewDataSourceType {

    associatedtype Element
    public func tableView(_ tableView: UITableView, observedEvent: RxSwift.Event<Self.Element>)

}
```

- `DataSource`を作成する

```swift
class DataSource: NSObject, RxTableViewDataSourceType, UITableViewDataSource {

    typealias Element = [[Item]]
        
    var items: Element = [[]]
        
    func tableView(_ tableView: UITableView, observedEvent: Event<Array<[Item]>>) {
        if case .next(let items) = observedEvent {
            self.items = items
            tableView.reloadData()
        }
    }
        
    // MARK: - UITableViewDataSource
       
    func numberOfSections(in tableView: UITableView) -> Int {
        return items.count
    }
        
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items[section].count
    }
        
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! ItemListViewCell
        let item = items[indexPath.section][indexPath.row]
        cell.item = item
        return cell
    }
}
```

- `DataSource`に`UITableViewDelegate`も実装する

```swift

class DataSource: NSObject, RxTableViewDataSourceType, UITableViewDataSource, UITableViewDelegate {

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headerView = tableView.dequeueReusableCell(withIdentifier: "Header") as! ItemHeaderViewCell
        return headerView
    }
        
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 120
    }
        
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 64
    }
}
```

#### Step 3

- `ViewModel`の`items`を`DataSource`に`Bind`すし、`delegate`をセットする

```swift
class ViewController: UITableViewController {
    private let viewModel = ViewModel()
    private let dataSource = DataSource()

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel.items
            .bindTo(tableView.rx.items(dataSource: dataSource))
            .addDisposableTo(disposeBag)

        tableView.delegate = dataSource
    }
}
```

### この3 StepでViewModel側で扱っている配列をSectionやHeaderViewを差し込みつつTableViewにバインドできる。

## DataSourceを使った場合TableViewをタップなどのアクションに対応する

#### `DataSource`に`output`の`subject`を用意するだけ！

```swift
class DataSource: NSObject, RxTableViewDataSourceType, UITableViewDataSource, UITableViewDelegate {

    private let selectedIndexPath = PublishSubject<IndexPath>()

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        selectedIndexPath.onNext(indexPath)
    }
}
```

#### この`subject`を`ViewController`で`subscribe`することでタップされた`IndexPath`を取得でき、これを使ってごにょごにょできる。

```swift
class ViewController: UITableViewController {

    private let viewModel = ViewModel()
    private let dataSource = DataSource()

    override func viewDidLoad() {
        super.viewDidLoad()

        dataSource.selectedIndexPath
            .bindTo(....)
            .addDisposableTo(disposeBag)
    }
}
```

## 配列+その他の値をDataSorceにBindしたい時はどうするのか


- `DataSource`に`Bind`する場合はObservableの型と`RxTableViewDataSourceType`の`Element`が同じでなければならない

```swift
protocol RxTableViewDataSourceType {

    associatedtype Element // これがObservableのTと同じである必要がある。
    public func tableView(_ tableView: UITableView, observedEvent: RxSwift.Event<Self.Element>)

}
```

#### つまり

この`Element`を`struct`などにすることで複数の値をまとめて`Bind`することができる。

##### associatedTypeはenumやstructにすることも可能です。

```swift
class DataSource: NSObject, RxTableViewDataSourceType, UITableViewDataSource {

    struct Element {
        let items: [[Item]]
        let user: User
    }
        
    var items: [Item] = [[]]
    var user: User? = nil
        
    func tableView(_ tableView: UITableView, observedEvent: Event<Array<[Item]>>) {
        if case .next(let element) = observedEvent {
            items = element.items
            user = element.user
            tableView.reloadData()
        }
    }
}
```

あとはViewControllerで`struct Element`を作り`Bind`するだけ！

```swift

class ViewController: UITableViewController {

    private let viewModel = ViewModel()
    private let dataSource = DataSource()

    override func viewDidLoad() {
        super.viewDidLoad()

        Observable
            .combineLatest(viewModel.user, viewModel.items) { DataSource.Element(items: $0, user: $1) }
            .bindTo(tableView.rx.items(dataSource: dataSource))
            .addDisposableTo(disposeBag)
    }
}
```

## 最後に

このTableViewでできることは全部CollectionViewでも実装可能です！

あと日本語版のRxSwiftが学習できるサンプルアプリ(or 質問とかできる場所)を作りたいと思ってるのですが、どなたか興味があればご連絡ください。全てのSNSは`yuzushioh`で検索できます。