Help us understand the problem. What is going on with this article?

RxSwiftでTableViewをハックする🏄

More than 3 years have passed since last update.

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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした