Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
78
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

@yuzushioh

RxSwiftでTableViewをハックする🏄

RxSwiftでTableViewをハックする🏄

RxSwiftでTableViewに配列を表示する2つの方法についてまとめてみました。

Observableの配列をTableViewにBindする方法

これが多分最も一般的な方法

  • ViewModelに定義されているObservableの配列をViewControllerTableViewBindする方法
class ViewModel {

    var items: Observable<Item> {
        return Observable.of(Item.dummyData)
    }
}
class ViewController: UITableViewController {

    private let viewModel = ViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel.items
            .bindTo(tableView.rx.items(cellIdentifier: "Cell", cellType: ItemListViewCell.self)) { index, item, cell in
                cell.item = item
            }
            .addDisposableTo(disposeBag)
    }
}

TableViewのSectionを分けたりHeaderViewを設定したりする方法

これがあまり使われていない方法

Step 1

  • ViewModelを作成する
class ViewModel {

    var elements: Observable<Item> {
        return Observable.of(Item.dummyData)
    }
}

Step 2

  • DataSourceViewControllerにネストで作成する。
このDataSourceRxTableViewDataSourceTypeに準拠する必要がある。
protocol RxTableViewDataSourceType {

    associatedtype Element
    public func tableView(_ tableView: UITableView, observedEvent: RxSwift.Event<Self.Element>)

}
  • DataSourceを作成する
class DataSource: NSObject, RxTableViewDataSourceType, UITableViewDataSource {

    typealias Element = [[Item]]

    var items: Element = [[]]

    func tableView(_ tableView: UITableView, observedEvent: Event<Array<[Item]>>) {
        if case .next(let items) = observedEvent {
            self.items = items
            tableView.reloadData()
        }
    }

    // MARK: - UITableViewDataSource

    func numberOfSections(in tableView: UITableView) -> Int {
        return items.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items[section].count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! ItemListViewCell
        let item = items[indexPath.section][indexPath.row]
        cell.item = item
        return cell
    }
}
  • DataSourceUITableViewDelegateも実装する

class DataSource: NSObject, RxTableViewDataSourceType, UITableViewDataSource, UITableViewDelegate {

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headerView = tableView.dequeueReusableCell(withIdentifier: "Header") as! ItemHeaderViewCell
        return headerView
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 120
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 64
    }
}

Step 3

  • ViewModelitemsDataSourceBindすし、delegateをセットする
class ViewController: UITableViewController {
    private let viewModel = ViewModel()
    private let dataSource = DataSource()

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel.items
            .bindTo(tableView.rx.items(dataSource: dataSource))
            .addDisposableTo(disposeBag)

        tableView.delegate = dataSource
    }
}

この3 StepでViewModel側で扱っている配列をSectionやHeaderViewを差し込みつつTableViewにバインドできる。

DataSourceを使った場合TableViewをタップなどのアクションに対応する

DataSourceoutputsubjectを用意するだけ!

class DataSource: NSObject, RxTableViewDataSourceType, UITableViewDataSource, UITableViewDelegate {

    private let selectedIndexPath = PublishSubject<IndexPath>()

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        selectedIndexPath.onNext(indexPath)
    }
}

このsubjectViewControllersubscribeすることでタップされたIndexPathを取得でき、これを使ってごにょごにょできる。

class ViewController: UITableViewController {

    private let viewModel = ViewModel()
    private let dataSource = DataSource()

    override func viewDidLoad() {
        super.viewDidLoad()

        dataSource.selectedIndexPath
            .bindTo(....)
            .addDisposableTo(disposeBag)
    }
}

配列+その他の値をDataSorceにBindしたい時はどうするのか

  • DataSourceBindする場合はObservableの型とRxTableViewDataSourceTypeElementが同じでなければならない
protocol RxTableViewDataSourceType {

    associatedtype Element // これがObservableのTと同じである必要がある。
    public func tableView(_ tableView: UITableView, observedEvent: RxSwift.Event<Self.Element>)

}

つまり

このElementstructなどにすることで複数の値をまとめてBindすることができる。

associatedTypeはenumやstructにすることも可能です。
class DataSource: NSObject, RxTableViewDataSourceType, UITableViewDataSource {

    struct Element {
        let items: [[Item]]
        let user: User
    }

    var items: [Item] = [[]]
    var user: User? = nil

    func tableView(_ tableView: UITableView, observedEvent: Event<Array<[Item]>>) {
        if case .next(let element) = observedEvent {
            items = element.items
            user = element.user
            tableView.reloadData()
        }
    }
}

あとはViewControllerでstruct Elementを作りBindするだけ!


class ViewController: UITableViewController {

    private let viewModel = ViewModel()
    private let dataSource = DataSource()

    override func viewDidLoad() {
        super.viewDidLoad()

        Observable
            .combineLatest(viewModel.user, viewModel.items) { DataSource.Element(items: $0, user: $1) }
            .bindTo(tableView.rx.items(dataSource: dataSource))
            .addDisposableTo(disposeBag)
    }
}

最後に

このTableViewでできることは全部CollectionViewでも実装可能です!

あと日本語版のRxSwiftが学習できるサンプルアプリ(or 質問とかできる場所)を作りたいと思ってるのですが、どなたか興味があればご連絡ください。全てのSNSはyuzushiohで検索できます。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
78
Help us understand the problem. What are the problem?