RxTableView入門

前言

RxSwift的魅力想必用過的人都有心得體會,簡直就是從入門到想放棄,從想放棄到愛不釋手的過程。但是RxSwift的前世今生并不是本文想寫的內(nèi)容,而是其中很常用又很重要的一個部分UITableView。不管用什么語言開發(fā)移動端App,新人們基本都會被告知,掌握了TableView和CollectionView,就學(xué)會了這門語言的80%,可想而知其重要性。

RxSwift的Git社區(qū),其中一個庫RxDataSources是本文的重頭戲。UITableViewDataSourceUITableViewDelegate是UITableView兩個重要的代理,RxDataSources用RxSwift封裝了tableView(:cellForRowAt)和tableView(:didSelectRowAt)方法,后面會細(xì)細(xì)道來。


正文

本文將從一個最最簡單的TableView例子講起,并將之用RxSwift實現(xiàn),然后一步步將其功能完善。當(dāng)然靈感都是來自于RxSwift官方Demo,demo中有簡單的RxTableView(注:基于RxSwift的UITableView,筆者稱之為RxTableView)例子。

第一個VC:Simplest UITableView

UITableViewDataSource和UITableViewDelegate是UITableView的核心,簡單說明下幾個最重要的代理方法:

// MARK: - UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
    return 1    // Section的數(shù)量
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return items.count    // Section中Row的數(shù)量
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) // TableViewCell繪制
    cell.textLabel?.text = items[indexPath.row]
    return cell
}
// MARK: - UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)    // TableViewCell點擊事件
    let viewController = RxTableViewController()
    viewController.type = RxTableViewType(rawValue: indexPath.row)!
    self.navigationController?.pushViewController(viewController, animated: true)
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 40    // TableViewCell高度
}
第二個VC:Simplest RXTableView

RxDataSources庫把UITableViewDataSource和UITableViewDelegate的代理封裝成了響應(yīng)式編程的風(fēng)格,在代碼量上少了很多,當(dāng)然也更難理解:

// 1.將數(shù)據(jù)綁定到TableView上
let items = Observable.just((0..<30).map({ "\($0)"}))
items.bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { (row, element, cell) in
    cell.textLabel?.text = "row \(element)"
    }.disposed(by: disposeBag)
// 2.TableViewCell點擊事件響應(yīng)
tableView.rx.modelSelected(String.self).subscribe(onNext: { (item) in
    print(item)    // "1"
}).disposed(by: disposeBag)

幾行代碼就可以實現(xiàn)cell的繪制和點擊處理,被觀察者Obserable有一個bind(to:)方法,將自己與訂閱者Observer綁定,在Obserable發(fā)送事件的時候,Observer會同步更新,兩者的數(shù)據(jù)類型必須保持一致。例如Obserable<String>類型的變量str,調(diào)用bind(to:)方法將其綁定到一個UILabel類型的變量label的text屬性上(同為String類型),那么str變化的同時, label.text的值跟str保持一致。

var str = "str"
let label = UILabel(frame: .zero)
Observable.of(str).bind(to: label.rx.text).disposed(by: disposeBag) // label.text = "str"
str = "changed"                                                     // label.text = "changed"

實質(zhì)上,bind(to:)是對subsribe做了一層封裝,subscribe(onNext:)同樣可以實現(xiàn)上述功能:

Observable.of(str).subscribe(onNext: { label.text = $0 }).disposed(by: disposeBag)

明顯,bind(to:)比subscribe(onNext:)更加"響應(yīng)式"。

第三個VC:RXTableView of Sections

用items(cellIdentifier:cellType:)方法可以滿足單個Section的場景,它還有另外一個方法items(dataSource:),則可以滿足多個Section的場景,當(dāng)然步驟也更加復(fù)雜。

// 自定義Model,遵循SectionModelType
struct SectionModel<HeaderType, ItemType>: SectionModelType {
    var header: HeaderType
    var items: [ItemType]
    
    init(header: HeaderType, items: [ItemType]) {
        self.header = header
        self.items = items
    }
    
    init(original: SectionModel<HeaderType, ItemType>, items: [ItemType]) {
        self.header = original.header
        self.items = items
    }
}
// 1.創(chuàng)建DataSource
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String,Int>>(configureCell: { (section, tableView, indexPath, element) in
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
    cell.textLabel?.text = "row \(element)"
    return cell
})
// 2.設(shè)置HeaderTitle(可選)
dataSource.titleForHeaderInSection = { (dataSource, sectionIndex) -> String? in
    return dataSource[sectionIndex].header
}
// 3.將數(shù)據(jù)綁定到TableView上
let items = [SectionModel(header: "section 1", items: [1, 2, 3]), SectionModel(header: "section 2", items: [1, 2, 3])]
Observable.just(items).bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: disposeBag)
// 4.TableViewCell點擊事件響應(yīng)
tableView.rx.itemSelected.map {
    return ($0, items[$0.section].header, dataSource[$0])
}.subscribe(onNext: { indexPath, header, item in
    print("\(header), row \(item)")    // "section 1, row 1"
}).disposed(by: disposeBag)

相對于上一個VC的直接綁定,這里需要額外定義Model,創(chuàng)建DataSource,并調(diào)用tableView.rx.items(dataSource:)方法來進(jìn)行綁定,這些額外的步驟都是為了提高可定制化的程度。這里Cell的點擊事件響應(yīng)已經(jīng)由modelSelected()方法換成了itemSelected()方法,差別就是modelSelected()方法會直接返回選中的model,而不會返回選中model的indexPath。


RxDataSources的優(yōu)劣

RxDataSources庫已經(jīng)提供了強大的功能來滿足RxSwift開發(fā)者們實現(xiàn)響應(yīng)式TableView的迫切愿望,但是也有不完美的地方,筆者將結(jié)合自身經(jīng)歷列出優(yōu)勢和不足:

優(yōu)勢
  1. 與RxSwift結(jié)合,讓代碼更加“響應(yīng)式”。即使用復(fù)寫delegate方法來實現(xiàn)UITableViewDataSource和UITableViewDelegate也是完全OK的,只是面向?qū)ο缶幊膛c響應(yīng)式編程的代碼糅合在一起,顯示特別奇怪。一切皆“響應(yīng)”,沒有什么是響應(yīng)式編程無法實現(xiàn)的,特別不能用面向?qū)ο蟮乃季S去思考,會越走越遠(yuǎn)。
  2. 類似閉包語法的風(fēng)格,讓代碼更加簡潔。以前筆者喜歡用delegate,覺得邏輯很簡單直觀,但是久了就會發(fā)現(xiàn)調(diào)試起來,頁面上下滾動太頻繁,對象的創(chuàng)建和代理方法調(diào)用基本不在同一頁面。RxDataSources這種“閉包式”的語法,讓代碼變得更加簡潔,更易調(diào)試。
  3. 其他優(yōu)點應(yīng)該還有很多。。。。。
不足

尚未完全Rx化,不得不復(fù)寫代理方法。筆者用了一段時間這個庫,目前發(fā)現(xiàn)有兩點是無法做到響應(yīng)式實現(xiàn)

  1. 通常在調(diào)用tableView(:didSelectRowAt:)方法后,會調(diào)用deselectRow(at:animated:) 函數(shù)來取消選中,但是目前沒有發(fā)現(xiàn)調(diào)用modelSelected()或itemSelected()方法后有什么方法可以直接取消選中,而不是在subscribe(onNext:)方法中用面向?qū)ο蟮倪壿媽崿F(xiàn);
  2. tableView(: heightForRowAt: )方法是用來設(shè)置高度的,RxTableViewSectionedReloadDataSource的配置項中目前沒有發(fā)現(xiàn)有什么方法可以設(shè)置Cell高度。

以上都是個人觀點,假如是因為筆者才疏學(xué)淺才沒有發(fā)現(xiàn)的話,還望指正。


總結(jié)

RxTableView的入門講解就告一段落,后續(xù)會出一篇進(jìn)階篇,深入分析數(shù)據(jù)和視圖的綁定機制,并實現(xiàn)一(數(shù)據(jù))對多(視圖)的綁定,一(數(shù)據(jù))對一(多視圖切換)的綁定,也是自己在實際項目中遇到的坑,拿出來跟各位共同探討。

Demo地址:https://github.com/MrSuperJJ/RxTableViewDemo

Git地址:https://github.com/MrSuperJJ

簡書地址:http://www.lxweimin.com/u/9079457bd1de

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,591評論 8 265
  • 概述在iOS開發(fā)中UITableView可以說是使用最廣泛的控件,我們平時使用的軟件中到處都可以看到它的影子,類似...
    liudhkk閱讀 9,088評論 3 38
  • 奈良第二日,照舊晚起,照舊亂逛。租了兩輛自行車,漫無目的地騎在冬日暖陽里。騎過正在修復(fù)的平城宮,騎過數(shù)條鐵軌,騎過...
    諾拉的以后閱讀 224評論 0 1
  • 前幾天一桌媒體吃飯,大家聊起了米錘之類的手機,不免要問我為什么那么看,我跟他們說完了,他們都表示非常認(rèn)同我的觀點。...
    孫見陽閱讀 287評論 0 1
  • 看了天氣預(yù)報 海口15日刮臺風(fēng) 于是馬不停蹄的趕往了三亞 一路上的狂風(fēng)暴雨 這天氣預(yù)報怎么跟翻書似的 一點也不靠譜...
    林恩恩恩閱讀 114評論 0 0