以往我们使用 UITableView 真的很麻烦,要处理 Cell 的个数啊、Cell UI 的绑定,万一写错了,一个数组越界,这也是难免的事情。这些相关的东西都是通过 dataSource
和 delegate
处理的。换成 closure 最直观的优势就是可以方便的在一个 ViewController 里写多个 TableView 了,再也不需要麻烦的判断机制了。
很快我们就可以看到这不仅仅是 closure 一个小事情,你会在这里体验到泛型的强大。
本篇内容可能有些长,但基本不是什么复杂的东西,多看看代码理解感觉一下就 OK 了。本篇示例均使用 UITableViewController
,当然这和其他方式区别不大,唯一的区别就是设置 delegate
和 dataSource
为 nil ,为了提醒你这件事的严重性。。。。
创建一个最基本的 TableView
dataSource.asObservable()
.bindTo(tableView.rx_itemsWithCellIdentifier("BasicCell", cellType: BasicTableViewCell.self)) { (_, element, cell) in
cell.nameLabel.text = element.name
cell.ageLabel.text = String(element.age)
}.addDisposableTo(disposeBag)
dataSource.value.appendContentsOf(BasicTableViewController.initialValue)
[RxTableView] -> [Basic] -> [BasicViewController.swift]
对,我是故意把数据的添加放在后面的,为的就是让你看到这里使用 Rx 的优越性有多大。
好了,开始正式的讲解,这里我建议你打开工程看完整的项目哦,这里涉及了三个文件,分别是 BasicViewController.swift
、 BasicModel.swift
、 BasicTableViewCell
。
这里的 Model 非常简单,就是一些属性 name
age
。
Variable
我们后面经常会用到这个 Variable
类,这里我们先来了解一下这个类是什么。它就是 BehaviorSubject
的一个封装,唯一的不同是 Variable
不会因为异常终止。
BehaviorSubject:当观察者订阅 BehaviorSubject 时,它开始发射原始 Observable 最近发射的数据(如果此时还没有收到任何数据,它会发射一个默认值),然后继续发射其它任何来自原始 Observable 的数据。
好啦,说简单点就是 Variable
的属性 value
只要有改变,就会发射数据,在这里就是更新 TableView 。我保证下下篇我们就开始各种知识点和概念的介绍,妥妥的。
BindTo
和上一章节的 bindTo
基本是起到一个效果的,都是将数据实时的绑定到 View 上。可以看到我们用了 rx_itemsWithCellIdentifier(_:_)
。这是一个柯里化(Currying):
“Swift 里可以将方法进行柯里化 (Currying),也就是把接受多个参数的方法变换成接受第一个参数的方法,并且返回接受余下的参数并且返回结果的新方法。”
摘录来自: 王巍 (onevcat). “Swifter - 100 个 Swift 必备 Tips (第二版)”。 iBooks.
完整的 rx_itemsWithCellIdentifier(_:_)
:
public func rx_itemsWithCellIdentifier<S: SequenceType, Cell: UICollectionViewCell, O : ObservableType where O.E == S>
(cellIdentifier: String, cellType: Cell.Type = Cell.self)
-> (source: O)
-> (configureCell: (Int, S.Generator.Element, Cell) -> Void)
-> Disposable
最终在 bindTo
后返回的就是 (configureCell: (Int, S.Generator.Element, Cell) -> Void) -> Disposable
,传入一个 configureCell
的闭包。
简单一点理解呢,就是说我们需要在 bindTo(_:_)
后面加上一个闭包,这个闭包就是处理 cell 的实现,对于 rx_itemsWithCellIdentifier(_:_)
,这个闭包的参数分别对应的 indexPath
、 element
、 cell
,我们在这里就获得了前面对应好数据的 item ,同时由于我们在 rx_itemsWithCellIdentifier(_:_)
中指出了 cell 的类型,闭包中既不需要再去整理数据,也不需要 as! BasicTableViewCell
了。简洁简洁,而且不容易写出数据绑定出错的问题。
不要忘了,这里我们都不需要再去指定 cell 的数量等等,这些都是容易在写代码时出错的地方。
进行了这样一个 Model 到 View 的绑定,以后的事情我们只需要关注 dataSource.value
的改变就可以了。
最基本的使用就是这些了,其实完全可以复制粘贴,修修改改拿来用,但是且慢,这不是我推荐的方式,请看下一节。在本节即将结束之前,我加一个小插曲:
我在代码中注释掉了一行,去掉注释运行一下看看效果?每个人都按照年龄从大到小排序了,没错,就是这样方便强大。
创建一个带 Section 的 TableView
嘿~这一节的代码才适合复制粘贴,修修改改拿来用呢。
let tvDataSource = RxTableViewSectionedReloadDataSource<TableSectionModel>()
tvDataSource.configureCell = { (_, tv, ip, i) in
let cell = tv.dequeueReusableCellWithIdentifier("SectionsCell") as! SectionsTableViewCell
cell.nameLabel.text = i.name
cell.ageLabel.text = String(i.age)
return cell
}
sections.asObservable()
.bindTo(tableView.rx_itemsWithDataSource(tvDataSource))
.addDisposableTo(disposeBag)
[RxTableView] -> [Sections] -> [SectionsViewController.swift]
这里 SectionsModel
是每个 Cell 对应的 Model ,TableSectionModel
是每个 Section 对应的 Model 。我们知道每个 Section 里有多个 Cell。于是这里的 TableSectionModel
实际的姿势是:
AnimatableSectionModel<Section : Hashable, ItemType : Hashable>
public typealias Item = RxDataSources.IdentifiableValue<ItemType>
public typealias Identity = Section
public var model: Section
public var items: [RxDataSources.IdentifiableValue<ItemType>]
如你所见,我们可以看做是这个 TableSectionModel
里面实际上装了一个 SectionCell 的 Model ,同时带着一个数组,里面装了对应 Section 的所有 CellModel 。
Note:
我们在这里使用了 RxDataSources ,AnimatableSectionModel
就是这个库中提供的,当然你也可以实现你自己的 SectionModel 。
好了,现在的 TableSectionModel
就是我们需要的数据模型:
sections.asObservable()
.bindTo(tableView.rx_itemsWithDataSource(tvDataSource))
.addDisposableTo(disposeBag)
接下来只差处理 Cell 那部分的东西了:
let tvDataSource = RxTableViewSectionedReloadDataSource<TableSectionModel>()
tvDataSource.configureCell = { (_, tv, ip, i) in
let cell = tv.dequeueReusableCellWithIdentifier("SectionsCell") as! SectionsTableViewCell
cell.nameLabel.text = i.value.name
cell.ageLabel.text = String(i.value.age)
return cell
}
tv 指 tableView ,ip 指 indexPath ,i 指对应的 Model ,通过 value 获取我们需要的数据。
基本上书写姿势就是这样了,当然你也可以参考官方的姿势,写一个 static func configureDataSource() -> RxTableViewSectionedReloadDataSource<SectionModel<String, User>>
或者 func skinTableViewDataSource(dataSource: RxTableViewSectionedDataSource<NumberSection>)
。
本小节的代码更具有一定规范性,定义了 CellModel
、SectionModel
、 DataSource
等,比较正确的姿势使用了泛型,这里类型都是安全的,唯独和上一节不同的就是 configureCell
了,但是我们这样写,就可以更好的定制多样的 Cell,可能有需求是一个 Section 中显示多个不同类型的 Cell ,但就目前来看,这是一种妥协吧。
关于 SectionCell 什么的,我会在后面的小节介绍,也就是 Advance 小节
Select
处理 Cell 的点击事件还是很常见的需求,这里使用起来也是非常方便的。
tableView.rx_itemSelected
.subscribeNext { indexPath in
let userInfo = SelectTableViewController.initialValue[indexPath.row]
Alert.showInfo(userInfo.name, message: "\(userInfo.age)")
}.addDisposableTo(disposeBag)
我们还有 rx_itemDeselected
、rx_itemInserted
、rx_itemDeleted
等等都可以使用。这里的处理方式暂时还不是最佳姿势,我们会在后面的章节了解更优美的处理方式。
更优美的处理方式 rx_modelSelected
可以在上面的代码中看到,我们需要的数据还是从其他地方拿了,这并不是很好。我们还有更优美的方法 rx_modelSelected
,用这个方法有什么好处呢?和前边生成 Cell 的类似,我们直接拿到了 Cell 对应的 Model ,通过泛型我们还在这里定义了 Model 的类型,后面的处理就都是静态了。最重要的一点是我们在前面的方法中去拿整个事件流之外的数据了,虽然说这里的处理是在观察者这个地方。
注意使用这个方法的一个前提是,绑定的 DataSource 需要服从 SectionedViewDataSourceType
,可以放心,我们用的大部分方法都已经实现了这个协议。
tableView.rx_modelSelected(BasicModel)
.subscribeNext { model in
Alert.showInfo(model.name, message: "\(model.age)")
}.addDisposableTo(disposeBag)
[RxTableView] -> [Basic] -> [BasicTableViewController.swift]
是不是感觉用起来很清爽直接呢?
在 RxDataSources 更新到 0.8 版本后,这个问题已经不存在,我们有了更优雅的写法。
如果你也这样直接去在 rx_itemsWithDataSource
方法下建立的 Cell ,直接这样使用基本会 Crash 的。 Error 信息是这个姿势:
fatal error: Failure converting from IdentifiableValue<SelectModel>(value: Andy's age is 24) to SelectModel: file /Users/Qing/Desktop/LearnRxSwift/Pods/RxCocoa/RxCocoa/Common/RxCocoa.swift, line 340
写成这样就 OK 了~:
tableView.rx_modelSelected(IdentifiableValue<SelectModel>)
.subscribeNext { model in
Alert.showInfo(model.identity.name, message: "\(model.identity.age)")
}.addDisposableTo(disposeBag)
至于原因,我们放在 [番外 102 更优雅的处理 TableView Select]() 中讨论。
Advance
这一部分我们做一个更复杂的 TableView ,
Basic Section
dataSource.titleForHeaderInSection = { [unowned dataSource] sectionIndex in
return dataSource.sectionAtIndex(sectionIndex).model
}
[RxTableView] -> [Advance] -> [AdvanceTableViewController.swift]
这部分我是注释掉的,大多 APP 好像都不会这样处理哈~
Custom Section
比较可惜的是,目前定制 Cell 的高 、以及定制 SectionView 仍然是通过 delegate 的方式:
tableView.rx_setDelegate(self)
[RxTableView] -> [Advance] -> [AdvanceTableViewController.swift]
记得设置要用这样的方式设置一下 delegate 。
到此,TableView 的基本使用就是这样了。下一章我们分别使用 Alamofire 和 Moya 来做一个网络层的处理。
参考阅读