やること
- RxDataSourcesライブラリを使ったMVVMサンプルアプリを作る
- UITableViewを使ったサンプル
環境
- macOS HighSierra
- Xcode 9.4
- iOS 11.4
- Swift 4.1
- cocoapods 1.5.3
完成イメージ
repo
手順
環境構築
- 新規プロジェクトをSingleViewAppで作成
- ライブラリを導入する
vi Podfile
platform :ios, '11.4'
use_frameworks!
target 'RxDataSourceExample' do
pod 'RxDataSources', '~> 3.0'
end
pod install
開発
- storyboard無しで開発を進める
- https://qiita.com/rika-tawashi/items/d975c2d9f85e8bb4aef5
- 👆を参考にInfo.plistの設定をやっておく
Modelを作成する
- 表示するデータを定義
Persion.swift
struct Person {
let name: String
}
- SectionModelを作成する
- SectionModelTypeプロトコルに準拠する構造体でセクションを定義する
新しいクラスを作っても良いけど、自分はViewModelに書いたCustomViewModel.swift
struct SectionOfPerson {
var header: String
var items: [Item]
}
extension SectionOfPerson: SectionModelType {
typealias Item = Person
init(original: SectionOfPerson, items: [SectionOfPerson.Item]) {
self = original
self.items = items
}
}
-
SectionOfPerson
のheader
がSectionのTitleで、items
がSection内のセルデータ群- このサンプルアプリでは、
header
にsection 1
、items
にPersion(name: “Nozaki”)
などが入る
- このサンプルアプリでは、
ViewController, ViewModelを作成
MVVMアーキテクチャ
ViewModelの作成
CustomViewModel.swift
class CustomViewModel {
let items = PublishSubject<[SectionOfPerson]>()
func updateItem() {
var sections: [SectionOfPerson] = []
sections.append(SectionOfPerson(header: "section 1", items: [SectionOfPerson.Item(name: "Nozaki"), SectionOfPerson.Item(name: "Sakura")]))
sections.append(SectionOfPerson(header: "section 2", items: [SectionOfPerson.Item(name: "Kashima"), SectionOfPerson.Item(name: "Hori")]))
sections.append(SectionOfPerson(header: "section 3", items: [SectionOfPerson.Item(name: "Seo"), SectionOfPerson.Item(name: "Wakamatsu")]))
items.onNext(sections)
}
}
CustomViewController.swift
import UIKit
import RxSwift
import RxDataSources
class CustomViewController: UIViewController, UITableViewDelegate {
@IBOutlet weak var tableView: UITableView!
private var disposeBag = DisposeBag()
private lazy var dataSource = RxTableViewSectionedReloadDataSource<SectionOfPerson>(configureCell: configureCell, titleForHeaderInSection: titleForHeaderInSection)
private lazy var configureCell: RxTableViewSectionedReloadDataSource<SectionOfPerson>.ConfigureCell = { [weak self] (dataSource, tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = person.name
return cell
}
private lazy var titleForHeaderInSection: RxTableViewSectionedReloadDataSource<SectionOfPerson>.TitleForHeaderInSection = { [weak self] (dataSource, indexPath) in
return dataSource.sectionModels[indexPath].header
}
private var viewModel: CustomViewModel!
override func viewDidLoad() {
super.viewDidLoad()
setupViewController()
setupTableView()
setupViewModel()
}
}
extension CustomViewController {
private func setupViewController() {
self.title = "タイトル"
}
private func setupTableView() {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.rx.setDelegate(self).disposed(by: disposeBag)
}
private func setupViewModel() {
viewModel = CustomViewModel()
viewModel.items
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
viewModel.updateItem()
}
}
- 上記のコードで動くようになる、以下簡単なコメント
抜粋CustomViewController.swift
# すごいやつ。
# こいつとデータのPublishSubjectをbindすると`tableReload`や`numberOfSections`をよしなにやってくれる
private lazy var dataSource = RxTableViewSectionedReloadDataSource<SectionOfPerson>(configureCell: configureCell, titleForHeaderInSection: titleForHeaderInSection)
# delegateでいう `cellForRowAt` の部分
private lazy var configureCell: RxTableViewSectionedReloadDataSource<SectionOfPerson>.ConfigureCell = { [weak self] (dataSource, tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = person.name
return cell
}
# delegateでいう `titleForHeaderInSection` の部分
private lazy var titleForHeaderInSection: RxTableViewSectionedReloadDataSource<SectionOfPerson>.TitleForHeaderInSection = { [weak self] (dataSource, indexPath) in
return dataSource.sectionModels[indexPath].header
}
// ~~~~~~
# SectionModelとdataSourceをbindさせる
viewModel.items
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
わかった
- MVVMとめちゃ相性良い
- 導入コスト高い
- RxSwift初心者(1ヶ月ほど)の自分でもこのサンプルアプリを作るのに4.5Hくらいかかった
- そこまでごりごりUITableViewを使うようなアプリじゃないなら、わざわざ導入するまでもなさそう
- 普通のDelegateメソッド使ったほうが早い (慣れだけど)
- 今回は標準の
RxTableViewSectionedReloadDataSource
を使ったが、データソースが変更された場合に自動的にアニメーションしながら更新してくれるAnimated Data Sources
という仕組みもある模様 => ★- 少し書き方を変更しなければいけないけど
次にやる
- 開発中の個人プロダクトにも入れてみるぞ 💪
- 気が向いたら
Animated Data Sources
を触ってみるぞ! -
UICollectionView
の場合の記事も書きたい