RxDataSources で複数のSectionに複数のItemを管理する。

[RxDataSources](https://github.com/RxSwiftCommunity/RxDataSources)を使って、複数のSectionで複数のItemを管理する方法の備忘録です。

# RxDataSourcesの使い方

## DataSourceについて

RxDataSourcesにRxSwiftの`RxCollectionViewDataSourceType` `RxTableViewDataSourceType` に準拠しているclassが用意されている。
これで、データソースを作成し`rx.items(dataSource:)` にbindする。

- UICollectionViewの場合
    - RxCollectionViewSectionedReloadDataSource\<S: SectionModelType>
    - RxCollectionViewSectionedAnimatedDataSource\<S: AnimatableSectionModelType>

- UITableViewの場合
    - RxTableViewSectionedReloadDataSource\<S: SectionModelType>
    - RxTableViewSectionedAnimatedDataSource\<S: AnimatableSectionModelType>

## SectionModelTypeについて

各DataSourceを使うには、`SectionModelType`、もしくは`AnimatableSectionModelType`に準拠している型を用意する必要があります。
この中の`items`がそのSection内のセルモデルにあたります。

```
protocol SectionModelType {
    associatedtype Item
    var items: [Item] { get }
    init(original: Self, items: [Item])
}
```

※AnimatableSectionModelTypeの場合は、自身が`IdentifiableType`に準拠しているかつ、Itemが`IdentifiableType`, `Equatable` に準拠している必要があります。

```
protocol AnimatableSectionModelType: SectionModelType, IdentifiableType where Item: IdentifiableType, Item: Equatable {}
```

```
protocol IdentifiableType {
    associatedtype Identity: Hashable
    var identity : Identity { get }
}
```

RxDataSourcesでは、上記それぞれに準拠している以下の型が用意されています。

```
struct SectionModel<Section, ItemType> {
    var model: Section
    var items: [Item]

    init(model: Section, items: [Item]) {
        self.model = model
        self.items = items
    }
}

struct AnimatableSectionModel<Section: IdentifiableType, ItemType: IdentifiableType & Equatable> {
    var model: Section
    var items: [Item]
    init(model: Section, items: [ItemType]) {
        self.model = model
        self.items = items
    }
}
```

## 実装してみる

### 1つのセクション

用意されている、`AnimatableSectionModel<Section, ItemType>`を使って実装してみます。
まずは、`IdentifiableType`と`Equatable`に準拠している型を用意します。

```
struct Card: IdentifiableType, Equatable {
    typealias Identity = String
    var identity: String {
        return id
    }
    let id: String
    let color: UIColor
}
```

`AnimatableSectionModel<String, Card>`として扱うと以下のような形になります。
※Sectionは、 RxDataSourcesで `extension String : IdentifiableType` が用意されているのでそれを使ってます。

```
var dataSource: RxCollectionViewSectionedAnimatedDataSource<AnimatableSectionModel<String, Card>> {
    return RxCollectionViewSectionedAnimatedDataSource<AnimatableSectionModel<String, Card>>(
        configureCell: { _, collectionView, indexPath, card -> UICollectionViewCell in
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier:　"任意のidentifier", for: indexPath)!
            cell.backgroundColor = card.color
            return cell
    })
}

cards.asObservable()
    .map { cards: [Card] in
        [AnimatableSectionModel<String, Card>(model: "", items: cards)] 
    }
    .bind(to: collectionView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)
```

↓追加とシャッフルと削除をしてます。

|アニメーションあり(Section1つ)|
|:---:|
|<img src="https://qiita-image-store.s3.amazonaws.com/0/16417/a9557061-3a59-2a0c-05aa-63916daf9ea0.gif" width=320>|

### 複数のセクション（共通のセルモデル）

Sectionが複数ある場合は`AnimatableSectionModel<Section: IdentifiableType, ItemType: IdentifiableType & Equatable>`を複数渡せばOKです。

**ただし、同じidentifierを用いた場合、fatalError()になるので注意です。
※異なるSection、異なる型の場合でも同じidentifierだと駄目**

```
var dataSource: RxCollectionViewSectionedAnimatedDataSource<AnimatableSectionModel<String, Card>> {
    return RxCollectionViewSectionedAnimatedDataSource<AnimatableSectionModel<String, Card>>(
        configureCell: { model, collectionView, indexPath, card -> UICollectionViewCell in
            // Section毎にcellを返す
            let sectionModel = model.sectionModels[indexPath.section]
            switch sectionModel.model {
            case "1":
                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "任意のidentifier", for: indexPath)!
                cell.backgroundColor = card.color
                return cell
            case "2":
                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "任意のidentifier", for: indexPath)!
                cell.backgroundColor = card.color
                return cell
            default:
                return UICollectionViewCell()
            }
    })
}
cards.asObservable()
    .map { cards: [Card] in
        // 雑ですが同じ値を使います。idだけ重複しないよう`2_`を先頭に加えています。
        let copy = cards.map { Card(id: "2_\($0.id)", color: $0.color) }
        return [AnimatableSectionModel<String, Card>(model: "1", items: cards),
                AnimatableSectionModel<String, Card>(model: "2", items: copy)]
    }
    .bind(to: collectionView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)
```

|アニメーションあり(Section2つ)|
|:---:|
|<img src="https://qiita-image-store.s3.amazonaws.com/0/16417/45f1c3c6-bf82-beba-4a21-23f8f8aa2fb0.gif" width=320>|

### 複数のセクション（複数のセルモデル）

異なるセルモデルの場合は、型でラップなりキャストする用意する必要があります。（IdentifiableType, Equatableに準拠している）
今回はenumを用意してやってみます。

```
struct Item01: IdentifiableType, Equatable {
    typealias Identity = String
    var identity: Identity {
        return id
    }
    let id: String
    let color: UIColor
}

struct Item02: IdentifiableType, Equatable {
    typealias Identity = String
    var identity: Identity {
        return id
    }
    let id: String
    let color: UIColor
}

struct Item03: IdentifiableType, Equatable {
    typealias Identity = String
    var identity: Identity {
        return id
    }
    let id: String
    let color: UIColor
}
enum Item: IdentifiableType, Equatable {
    typealias Identity = String
    var identity: Identity {
        switch self {
        case .item01(let item):
            return item.identity
        case .item02(let item):
            return item.identity
        case .item03(let item):
            return item.identity
        }
    }
    case item01(Item01)
    case item02(Item02)
    case item03(Item03)
}
```

あとは、同じように`RxCollectionViewSectionedAnimatedDataSource`を実装します。
cellを生成する場所はSection（String）やItemでお好みに分岐でOKです。

```
var dataSource: RxCollectionViewSectionedAnimatedDataSource<AnimatableSectionModel<String, Item>> {
    return RxCollectionViewSectionedAnimatedDataSource<AnimatableSectionModel<String, Item>>(
        configureCell: { model, collectionView, indexPath, item -> UICollectionViewCell in
            let sectionModel = model.sectionModels[indexPath.section]
            switch sectionModel.model {
            case "0":
                switch item {
                case let .item01(i):
                    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "任意のidentifier", for: indexPath)
                    cell.backgroundColor = i.color
                    return cell
                case let .item02(i):
                    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "任意のidentifier", for: indexPath)
                    cell.backgroundColor = i.color
                    return cell
                case let .item03(i):
                    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "任意のidentifier", for: indexPath)
                    cell.backgroundColor = i.color
                    return cell
                }
            case "1":
                switch item {
                case let .item01(i):
                    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "任意のidentifier", for: indexPath)
                    cell.backgroundColor = i.color
                    return cell
                case let .item02(i):
                    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "任意のidentifier", for: indexPath)
                    cell.backgroundColor = i.color
                    return cell
                case let .item03(i):
                    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "任意のidentifier", for: indexPath)
                    cell.backgroundColor = i.color
                    return cell
                }
            default:
                return UICollectionViewCell()
            }
    })
}
Observable
    .combineLatest(item01.asObservable(),
                   item02.asObservable())
    .map { (item01: [Item], item02: [Item]) in
        return [AnimatableSectionModel<String, Item>(model: "0", items: item01),
                AnimatableSectionModel<String, Item>(model: "1", items: item02)]
    }
    .bind(to: collectionView.rx.items(dataSource: delegate.dataSource))
    .disposed(by: disposeBag)
```

|アニメーションあり(Section2つで複数のセルモデル)|
|:---:|
|<img src="https://qiita-image-store.s3.amazonaws.com/0/16417/91347a72-5660-df72-f76c-20c69c451b71.gif" width=320>|

以上です！
あとは、StringにしているSectionを型にした、cellの生成部分を整理すると良いと思います。

※おまけ (tvOS)

|アニメーションあり(Section2つで複数のセルモデル)|
|:---:|
|<img src="https://qiita-image-store.s3.amazonaws.com/0/16417/dd0397d9-bd92-9165-49a0-e3df3a8f45ad.gif">|