LoginSignup
5
2

More than 3 years have passed since last update.

RxDataSourcesでCollectionViewを実装する方法

Last updated at Posted at 2020-08-30

iOSアプリに欠かせないUITableViewやCollectionViewの表示&状態管理ですが、
自分は日常的にRxDataSourcesを使っており、今回はその実装方法について記録しようと思います。

前提

RxDataSources GitHub Repository

RxDataSourcesでは以下のようにSectionModelがstructで定義されているので、そのGenericTypesの二つの値を見て表示するSectionとCellを管理します。TestSectionModelをtypealeaseで定義し、enumでSectionのタイプとcellのタイプを定義するのが良いと思います。私は、以下の場合 TestSectionType = SectionTestSectionItemType = Cell という理解をしています。

typealias TestSectionModel = SectionModel<TestSectionType, TestSectionItemType>

enum TestSectionType { case testSection }
enum TestSectionItemType { case testCell(title: String) }

ViewModel

今回はサンプルコードのため、以下のようにしています

import RxCocoa
import RxSwift
import RxDataSources

typealias TestSectionModel = SectionModel<TestSectionType, TestSectionItemType>

enum TestSectionType { case testSection }
enum TestSectionItemType { case testCell(title: String) }

final class TestViewModel {
    var items = BehaviorRelay<[TestSectionModel]>(value: [])

    init() {
        // 通信処理が必要な場合は通信を監視し、完了してからconfigureItems()を呼ぶ
        configureItems()
    }

    private func configureItems() {
        var sections: [TestSectionModel] = []
        var cellItems: [TestSectionItemType] = []

        cellItems.append(.testCell(title: "タイトル"))
        sections.append(TestSectionModel(model: .testSection, items: cellItems))

        items.accept(sections)
    }
}

これらのSectionModelなどの状態やitemsへの格納処理は以下の理由からViewModelに処理を寄せるようにしています。

  1. configureItemsは通信結果やCellの構成によっては複雑かつ膨大な処理になる可能性が高く、Controllerで保持するとControllerがFatになる可能性がある
  2. 一般的にCollectionViewの状態は通信結果によって変わることが多いため、通信処理を実装するViewModelに寄せた方が役割がスッキリすると考えている

ViewController

final class TestViewController: UIViewController {
    private let viewModel: TestViewModel = TestViewModel()
    private let disposeBag = DisposeBag()

    private lazy var dataSource = RxCollectionViewSectionedReloadDataSource<TestSectionModel>(configureCell: configureCell)
    private lazy var configureCell: RxCollectionViewSectionedReloadDataSource<TestSectionModel>.ConfigureCell = {[weak self] (_, collectionView, indexPath, item) in
        return (self?.cell(collectionView, item: item, indexPath: indexPath) ?? UICollectionViewCell())
    }

    private func cell(_ collectionView: UICollectionView, item: TestSectionModel, indexPath: IndexPath) -> UICollectionViewCell? {
        switch item {
        case .testCell(let title):
            if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as? TestCollectionCell {
                cell.configureCell(title: title)
                return cell
            }
            return nil
        }
    }
}

ViewControllerのconfigureCell変数ですが、ここでtypealeaseとして定義したTestSectionModelをRxCollectionViewSectionedReloadDataSourceの型パラメータに定義することで、渡されたTestSectionModelによってどのcellを返すかを定義しています。
最終的にこの値を保持するのはdataSource変数ですが、ViewModelで定義した var items = BehaviorRelay<[TestSectionModel]>(value: []) と実際に表示しているCollectionViewの状態を一致させたいので、以下の処理でviewModel.itemsとcollectionViewとのバインディングを行います。

viewModel.items
    .asObservable()
    .bind(to: collectionView.rx.items(dataSource: dataSource)
    .disposed(by: disposeBag)

補足

  • sizeForItemAtやinsetForSectionAtなどの処理は標準で用意されているDelegateで実装する必要があるみたいなので、CellのサイズやSectionとCellの距離などを調整したい方は collectionView.delegate = self を行う必要があります。RxDataSourcesには同じ処理を以下のような形で表現できます。
collectionView.rx.setDelegate(self).disposed(by: disposeBag)

RxDataSourcesを活用するデメリット/メリット

デメリット

  • 慣れていない時は構成がわかりづらく、少ない要素のCollectionViewだと逆にこれまで通りDelegateで実装してしまった方が簡単
  • 膨大な種類のCellで複雑なレイアウトを実装しようと考えている際にはViewController内のconfigureCellの変数が膨大になってしまい、変数単体として見ると可読性が低下してしまう懸念がある

メリット

  • ViewModelとCollectionView間のバインディングが簡単に実装出来る
  • 今回は触れていませんが、 RxCollectionViewSectionedAnimatedDataSource を使うことによってcellの差分更新とアニメーションが簡単になる

まとめ

膨大な種類のCellで複雑なレイアウトを実装しようと考えている際にはViewController内のconfigureCellの変数が膨大になってしまい、

デメリットであげたこちらの懸念ですが、CollectionViewの設定まわり(これまでDelegateで実装していた箇所)のみ別ファイルに切り出すことによって解決できるのかなと感じました。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2