📖
【学習備忘録】RxDataSourcesを使ってリスト表示をする
はじめに
「比較して学ぶ RxSwift4 入門」のCounterアプリのコードをもとに学習しました。
RxDataSourcesを使ってUITableViewアプリを実装します。
サンプルコード
完成
1. SectionModel
- SettingsSectionでセクション数・headerとfooterの高さを決める。
- SettingsItemでセルに表示する内容を定義する。
- SectionModel<SettingsSection, SettingsItem>をtypealiasでSettingsSectionModelの型として定義する。
typealias SettingsSectionModel = SectionModel<SettingsSection, SettingsItem>
enum SettingsSection {
case account
case common
var headerHeight: CGFloat {
return 40.0
}
var footerHeight: CGFloat {
return 1.0
}
}
enum SettingsItem {
// account section
case account
case security
case notification
case contents
// common section
case sounds
case dataUsing
case accessibility
// other
case description(text: String)
var title: String? {
switch self {
case .account:
return "アカウント"
case .security:
return "セキュリティ"
case .notification:
return "通知"
case .contents:
return "コンテンツ設定"
case .sounds:
return "サウンド設定"
case .dataUsing:
return "データ利用時の設定"
case .accessibility:
return "アクセシビリティ"
case .description:
return nil
}
}
var rowHeight: CGFloat {
switch self {
case .description:
return 72.0
default:
return 48.0
}
}
var accessoryType: UITableViewCell.AccessoryType {
switch self {
case .account, .security, .notification, .contents, .sounds, .dataUsing, .accessibility:
return .disclosureIndicator
case .description:
return .none
}
}
}
2. SettingsViewModel
- 定数のitemは、.errorや.completedが流れてこないようBehaviorRelayで.nextだけ流せるようにする。.nextイベントを流すにはacceptメソッドを使う。
- accountSectionとcommonSectionの内容をupdateItems()でまとめる。
class SettingsViewModel {
let items = BehaviorRelay<[SettingsSectionModel]>(value: [])
var itemObsevable: Observable<[SettingsSectionModel]> {
return items.asObservable()
}
func setup() {
updateItems()
}
func updateItems() {
let sections: [SettingsSectionModel] = [
accountSection(),
commonSection()
]
items.accept(sections)
}
private func accountSection() -> SettingsSectionModel {
let items: [SettingsItem] = [
.account,
.security,
.notification,
.contents
]
return SettingsSectionModel(model: .account, items: items)
}
private func commonSection() -> SettingsSectionModel {
let items: [SettingsItem] = [
.sounds,
.dataUsing,
.accessibility,
.description(text: "基本設定はこの端末でログインしている全てのアカウントに適用されます。")
]
return SettingsSectionModel(model: .common, items: items)
}
}
3. ViewController
- @IBOutletでUITableViewを接続。
- セルの設定を変数のconfigureCellで行う。
- 設定したセルをdataSourceにセットする。
- lazy(遅延格納プロパティ)は、 利用するまで初期化処理を走らせないようにする。
- setupTableView()でtableViewの設定をする。
- cellの登録。
- contentInsetの設定。
- delegateで委任し、extentionでセルの高さなどを指定。
- タップ時の処理の設定。
- ViewModelの設定。
- ViewModelのitemsとtableViewのitemsを.bindする。
- updateItems()でデータを流す。
class SettingsViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
private var disposeBag = DisposeBag()
private lazy var dataSource = RxTableViewSectionedReloadDataSource<SettingsSectionModel>(configureCell: configureCell)
private lazy var configureCell: RxTableViewSectionedReloadDataSource<SettingsSectionModel>.ConfigureCell = {
[weak self] (dataSource, tableView, indexPath, _) in
let item = dataSource[indexPath]
switch item {
case .account, .security, .notification, .contents, .sounds, .dataUsing, .accessibility:
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = item.title
cell.accessoryType = item.accessoryType
return cell
case .description(let text):
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = text
cell.isUserInteractionEnabled = false
return cell
}
}
private var viewModel: SettingsViewModel!
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
setupViewModel()
}
private func setupTableView() {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.contentInset.bottom = 12.0
tableView.rx.setDelegate(self).disposed(by: disposeBag)
tableView.rx.itemSelected
.subscribe(onNext: { [weak self] indexPath in
guard let item = self?.dataSource[indexPath] else { return }
self?.tableView.deselectRow(at: indexPath, animated: true)
switch item {
case .account:
// 遷移させる処理
break
case .security:
break
case .notification:
break
case .contents:
break
case .sounds:
break
case .dataUsing:
break
case .accessibility:
break
case .description:
break
}
})
.disposed(by: disposeBag)
}
private func setupViewModel() {
viewModel = SettingsViewModel()
viewModel.items
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
viewModel.updateItems()
}
}
extension SettingsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let item = dataSource[indexPath]
return item.rowHeight
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
let section = dataSource[section]
return section.model.headerHeight
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
let section = dataSource[section]
return section.model.footerHeight
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = .clear
return headerView
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footerView = UIView()
footerView.backgroundColor = .clear
return footerView
}
}
GitHub
参考にしたもの
1.比較して学ぶRxSwift入門
さいごに
いやー、難しい...
処理を追いながら、説明を書き加えた(全く理解できない説明になっていると思います...)のですが、これを使いこなせる自信がないので、もっと精進します。
間違いや認識違いがあれば指摘いただければ幸いです。
Discussion