環境
- Xcode 12.4
- Swift 5.3.2
- RxSwift 6.0.0
- RxCocoa 6.0.0
- RxDataSources 5.0.0
前置き
RxSwift
とRxCocoa
に触れたことがあり、
RxDataSources
をこれから利用するかもしれない方に向けて書いています。
自分にとってRxSwift/RxCocoa同様、学習コストが高く苦労した覚えがあったので、
サンプルが一つでも多くあるといいなと思い、今回記事にしました。
RxSwiftとRxCocoaの記述についてはあまり触れないのでご了承ください
実装内容
SectionModelType
のtypealias Item
にはEnum(Associated value)を利用しています。
表示するデータはViewModelから適当に流すようにしています。
リロードする度に新しいセクション情報を構成して表示する仕様になっています。
iPhone8(iOS14.4) |
---|
実装サンプルコード
ViewController.swift
class ViewController: UIViewController {
private let viewModel = ViewModel()
private let tableView = UITableView()
private let refreshControl = UIRefreshControl()
private let refreshRelay = PublishRelay<Void>()
private let disposeBag = DisposeBag()
// ジェネリッククラスのRxTableViewSectionedReloadDataSourceの型パラメータには
// SectionModelTypeを適用したクラスを指定する
private let dataSource = RxTableViewSectionedReloadDataSource<SectionModel>(
// tableView(_:cellForRowAt:)で行っている処理
configureCell: { _, tableView, indexPath, item in
switch item {
case .stringSection(let text):
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else { fatalError() }
cell.textLabel?.text = text.rawValue
return cell
case .intSection(let number):
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else { fatalError() }
cell.textLabel?.text = "\(number.rawValue)"
return cell
}
},
// tableView(_:titleForHeaderInSection:)で行っている処理
titleForHeaderInSection: { dataSource, section in
dataSource.sectionModels[section].title
}
)
override func loadView() {
super.loadView()
view.backgroundColor = .systemBackground
refreshControl.addTarget(self, action: #selector(reloadTableView), for: .valueChanged)
tableView.refreshControl = refreshControl
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.tableFooterView = UIView()
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
}
override func viewDidLoad() {
super.viewDidLoad()
let viewDidLoadRelay = PublishSubject<Void>()
viewModel
.observeSectionInfo(viewDidLoad: viewDidLoadRelay,
refresh: refreshRelay)
.drive(tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
viewDidLoadRelay.onNext(())
}
@objc private func reloadTableView() {
refreshRelay.onNext(())
refreshControl.endRefreshing()
}
}
ViewModel.swift
// セクション情報
enum SectionItem {
case stringSection(StringSectionRowItem) // ①
case intSection(IntSectionRowItem) // ②
}
// ①の要素(row)
enum StringSectionRowItem: String {
case first
case second
case third
}
// ②の要素(row)
enum IntSectionRowItem: Int {
case first = 1
case second = 2
case third = 3
}
// SectionModelTypeを適用した構造体を定義する。
// sectionのtitle用のプロパティとinitを追加しています。
struct SectionModel: SectionModelType {
typealias Item = SectionItem
var items: [SectionItem]
// 追加したプロパティ
var title: String
init(original: SectionModel, items: [Item]) {
self = original
self.items = items
}
// 追加したinitializer
init(title: String, items: [Item]) {
self.title = title
self.items = items
}
}
// MARK: - ViewModel
struct ViewModel {
func observeSectionInfo(viewDidLoad: Observable<Void>,
refresh: Observable<Void>) -> Driver<[SectionModel]> {
Observable
.merge(viewDidLoad, refresh)
.map {
// section, rowの並びをシャッフルして返す
[
SectionModel(title: "String Section",
items: [.stringSection(.first),
.stringSection(.second),
.stringSection(.third)].shuffled()),
SectionModel(title: "Int Section",
items: [.intSection(.first),
.intSection(.second),
.intSection(.third)].shuffled())
]
.shuffled()
}
.asDriver(onErrorRecover: { _ in fatalError() })
}
}
終わりに
慣れるまで時間はかかりましたが、DelegateとDataSourceのメソッドをRxでまとめられるので便利だなと思っています(小並感)
section, rowがそこまで複雑ではない場合は利用しなくても良さそうです。