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つ) |
---|
複数のセクション(共通のセルモデル)
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つ) |
---|
複数のセクション(複数のセルモデル)
異なるセルモデルの場合は、型でラップなりキャストする用意する必要があります。(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つで複数のセルモデル) |
---|
以上です!
あとは、StringにしているSectionを型にした、cellの生成部分を整理すると良いと思います。
※おまけ (tvOS)
アニメーションあり(Section2つで複数のセルモデル) |
---|