LoginSignup
20
16

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-07-13

やること

  • 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 の場合の記事も書きたい
20
16
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
20
16