局部 Cell 刷新
本文是对在实践中应用 RxSwift 3 处理 Cell的补充,同时修正一些之前犯得错误(Cell ),Demo 地址:Thought。
ReactiveCocoa 提供了 rac_prepareForReuseSignal 属性,一般用来解决 Cell 重用问题(重复订阅导致点击效果多次相应)。但之前我一直是持有不赞同的观点,这一直让我感觉这样下面这段代码会增加理解难度。
[[[cell.okButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
takeUntil:cell.rac_prepareForReuseSignal]
subscribeNext:^(UIButton *x) {
MXThing *thing = self.things[indexPath.row];
// use this thing
}];
[[[cell.cancelButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
takeUntil:cell.rac_prepareForReuseSignal]
subscribeNext:^(UIButton *x) {
// do other things
}];
选择支付方式
然而前几天遇到这下面图片的需求。

选择支付方式,需要注意的是支付方式选项是动态的,比如可能没有支付宝支付。
类似完整的页面如下。

我去参考了一下上图的代码,看了十分钟后,,开始写代码。
每一个 Cell 需要两个信息。
- 支付方式信息 payment
- 是否选择该支付方式 isSelected
原本我的设计是一个 set 方法,设置支付方式信息,另一个是是否选择该支付方式。Cell 中的代码如下:
func setInfo(payment: Payment, isSelected: Bool) {
// ...
}看起来没什么问题,点击 Cell 的时候更新数据,在当前场景下我更新 Cell 的方式只有调用以下几种方法:
public func reloadData()
public func reloadSections(sections: NSIndexSet, withRowAnimation animation: UITableViewRowAnimation)
public func reloadRowsAtIndexPaths(indexPaths: [NSIndexPath], withRowAnimation animation: UITableViewRowAnimation)
看起来没什么问题,点击一次,刷新一下 UITableView ,然而这里存在一个很差的用户体验,点击效果很差。点击动画还未结束,Cell 就被重新加载了。不可以,这效果很糟糕,用了 Rx 会导致效果变差,如果真的是这样,这就非常尴尬了。
再讨论解决方案前,先考虑一个问题,有没有必要调用 reload 方法,该场景下没有必要。完成上述需求只需要做两件事情:
- 更新 Cell 中“√”颜色
- 更改数据源结果(确保 Cell 消失后再出现仍然是正确的“√”状态)
Cell 中的绑定
之前自己一直固执的认为 Cell 是不需要绑定的,因为它们经常被重用,经常出现又消失。而本例提到的场景并没有很强的重用、出现消失体现,更多体现在交互逻辑变化。
本例中存在两个绑定场景:
- 完整数据 -> UITableView
- 选择状态 -> [UITableViewCell]
第一个是显然的,来看第二个,Cell 需要两个信息 payment 和 isSelected ,payment 设置一下就好了,需要变的时候只需要 reload 。 isSelected 则不同,我们希望不 reload 就做到视图和数据的更新。
那么为什么不能在一个数据模型中加个 Observable 呢,由这个 Observable 通知 Cell 的更新,这里我们选择 Variable 完成这个需求。
那么如何实现这部分代码,开始之前回顾一下真实的例子:

选择支付方式是一个 Section ,将选择的信息放在 Section 是比较好的选择。
struct SelectPayment {
let select: Variable<Payment>
}item 对应的 Model 如下。
enum Payment {
/// 支付宝
case alipay
/// 微信
case wechat
/// 银联
case unionpay
}完整的 SectionModel 如下。
typealias PaymentSectionModel = AnimatableSectionModel<SelectPayment, Payment>
关键代码如下。
selectedPayment
.map { $0 == payment }
.bindTo(cell.rx_isSelectedPayment)
.addDisposableTo(cell.reusableDisposeBag)
唯一要注意的是,不要添加到 self.disposeBag ,即 addDisposeableTo(self.disposeBag),这样的代码做不到更新绑定规则。 reusableDisposeBag 的原理很简单。
override func prepareForReuse() {
super.prepareForReuse()
reusableDisposeBag = DisposeBag()
}重新设置 disposeBag ,取消之前所有的订阅。
购物车商品加减
处理商品加减在 Model 中更需要传入一个 Observable 了,而且这里还存在非常清晰的双向绑定,Item <-> Cell。

即每一个商品的购买数量和 Cell 存在绑定关系。完整的逻辑图如下。

重点关注这个绑定关系,Cell 中的加减行为只会影响到购买数量。

那么修改该变量则没必有必要影响其他数据的变化,即

至于代码的实现,仍然是在一个 ItemModel 中传入一个 Observable 。
struct ProductInfo {
let id: Int64
let name: String
let unitPrice: Int
let count: Variable<Int>
}Cell 的绑定关系只需一行代码。
(cell.rx_count <-> product.count).addDisposableTo(cell.reusableDisposeBag)
<-> 操作符的实现见:Operators.swift 。
彩蛋
我在本文的示例代码中添加了一些你可能没有注意到的 Swift 细节。
.subscribeNext(tableView.deselectRowAtIndexPath)
changeCount(-=)
总结
在需要绑定场景使用 RxSwift ,这是考虑要不要使用 RxSwift 的最佳考虑方案,需要优雅的处理异步不一定选择 RxSwift ,还可以考虑 PromiseKit 。
Rx 实际上是一个思想,一种面向(异步)数据流编程的思想,注重流的变化。
相关阅读
双向绑定
Issue
- ActivityIndicator part of RxCocoa #731
- Two-way binding in a separated project inside the RxSwiftCommunity repo? #771