2
1

More than 3 years have passed since last update.

Enumを用いたUITableView+RxDataSourcesの実装サンプル

Last updated at Posted at 2021-02-13

環境

  • Xcode 12.4
  • Swift 5.3.2
  • RxSwift 6.0.0
  • RxCocoa 6.0.0
  • RxDataSources 5.0.0

前置き

RxSwiftRxCocoaに触れたことがあり、
RxDataSourcesをこれから利用するかもしれない方に向けて書いています。

自分にとってRxSwift/RxCocoa同様、学習コストが高く苦労した覚えがあったので、
サンプルが一つでも多くあるといいなと思い、今回記事にしました。

RxSwiftとRxCocoaの記述についてはあまり触れないのでご了承ください:pray:

実装内容

SectionModelTypetypealias 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がそこまで複雑ではない場合は利用しなくても良さそうです。

2
1
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
2
1