Edited at

RxDataSourcesライブラリ+UITableViewを使ってMVVMサンプルiOSアプリを作る


やること


  • RxDataSourcesライブラリを使ったMVVMサンプルアプリを作る

  • UITableViewを使ったサンプル


環境


  • macOS HighSierra

  • Xcode 9.4

  • iOS 11.4

  • Swift 4.1

  • cocoapods 1.5.3


完成イメージ

Simulator Screen Shot - iPhone 8 - 2018-07-14 at 02.25.19.png


repo


手順


環境構築


  • 新規プロジェクトをSingleViewAppで作成

  • ライブラリを導入する

vi Podfile

platform :ios, '11.4'

use_frameworks!

target 'RxDataSourceExample' do
pod 'RxDataSources', '~> 3.0'
end

pod install


開発


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
}
}




  • SectionOfPersonheader がSectionのTitleで、 items がSection内のセルデータ群


    • このサンプルアプリでは、headersection 1itemsPersion(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 の場合の記事も書きたい