IGListKitとRxSwiftを掛け合わせてみた🤔
去年のTry Swift New Yorkで発表があったInstagramのFeedの実装に利用されているIGListKitをRxSwiftでUICollectionView
やUITableView
を扱うように実装できるようにしてみました。(Try Swiftでの発表はこちら)
現状で実装できているのはIGListKitの中でもIGListAdapterDataSource
のみで他のDataSourceやDelegateは実装できていません!良い方法を探しつつ更新していけたらと思います!
このプロジェクトはGithubに公開されているのでクローンして動かしてみてください!
-> https://github.com/yuzushioh/RxIGListKit
Background
RxSwiftを使っているプロジェクトでIGListKitを使うとどうしても、リストに表示するElement
の更新と、リストを更新するメソッドを呼ぶタイミングを、subscribe
する順番などを変えて考慮しないといけません!
もしリストに表示するElementの更新より先にリスト自体を更新してしまうと、バグやクラッシュに繋がります。
またリストに表示するElement
はObservable
であってはならないので、ViewModel側にプロパティーが1つ増えてしまい、このプロパティーも毎回更新する必要がでてきます。
👇👇👇👇👇👇
これを解決するためにObservable<Element>
をそのままDataSource
に繋ぎこめるようにしたかった🙏🏻
Example
今回は実際にIGListKitとRxSwiftを使って以下のUIを実装してみた。
プロジェクトはこのレポジトリーに公開しています。
About RxIGListKit
RxSwiftでUITableView
やUICollectionView
にはitems(dataSource: _)
メソッドが用意されていて、DataSourceを定義し、その中に指定したElement
のObservableをバインドするだけで更新ができます。
今回はIGListAdapterにitems(dataSource: _)
を実装しました🚀
- Step 1
UITableView
やUICollectionView
と同様にRxIGListAdapterDataSource
protocolを定義する
protocol RxIGListAdapterDataSource {
associatedtype Element
func listAdapter(_ adapter: IGListAdapter, observedEvent: Event<Element>) -> Void
}
- Step 2
IGListAdapterにReactive Extensionでitems(dataSource: _)
を追加! (サクッと実装なのでベストかはわかりません)
func items<DataSource: RxIGListAdapterDataSource & IGListAdapterDataSource,
O: ObservableType>(dataSource: DataSource)
-> (_ source: O)
-> Disposable where DataSource.Element == O.E {
return { source in
let subscription = source
.subscribe { dataSource.listAdapter(self.base, observedEvent: $0) }
return Disposables.create {
subscription.dispose()
}
}
}
- Step 3
あとはDataSourceを定義して繋ぎこむだけです👍
How to use
DataSourceを定義し、その中のElement
と同じ型のストリームを繋ぐだけでIGListKitで実装されたリストを更新することができます。
lazy privatevar adapter: IGListAdapter = {
return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 2)
}()
private let disposeBag = DisposeBag()
private let dataSource = DataSource()
override func viewDidLoad() {
super.viewDidLoad()
adapter.rx.setDataSource(dataSource)
.disposed(by: disposeBag)
viewModel.feeds
.drive(adapter.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
final class DataSource: NSObject, IGListAdapterDataSource, RxIGListAdapterDataSource {
typealias Element = [Foo]
var elements: Element = []
func listAdapter(_ adapter: IGListAdapter, observedEvent: Event<Element>) {
if case .next(let elements) = observedEvent {
self.elements = elements
adapter.performUpdates(animated: true)
}
}
func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
return elements
}
func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController {
return SectionController()
}
func emptyView(for listAdapter: IGListAdapter) -> UIView? {
return nil
}
}
Help
次はIGListSectionTypeをRxSwift化していきたいので何か良い方法があればコメントを頂けると幸いです。UICollectionView
やUITableView
のようにModelSelectedなどを追加できたらな〜と思います。