如何获取表示TableView是否存在选中行的Observable<Bool>?
解决RxDataSources下TableView选中行状态联动按钮显示的问题
我明白你遇到的痛点了——用RxDataSources驱动的TableView,要完美联动按钮的显示隐藏,得覆盖用户手动选中/取消和数据源更新导致选中行消失这两种核心场景,之前的两种方案都只覆盖了部分情况。下面给你一个完整的解决方案:
核心思路
我们需要创建一个能实时反映「是否存在有效选中行」的Observable<Bool>,这个Observable要监听所有可能改变选中状态的事件:
- 用户手动选中行
- 用户手动取消选中行
- 数据源更新(可能导致原有选中行被移除)
- TableView重新加载数据
每次事件触发时,我们都重新检查当前选中的indexPath是否在数据源的有效范围内,确保状态的准确性。
完整代码实现
假设你的SectionModel是自定义的YourSectionType,数据源Observable命名为sections(就是你绑定给RxTableViewSectionedAnimatedDataSource的那个流),代码如下:
// 1. 定义所有可能触发选中状态变化的事件源 let itemSelectedTrigger = tableView.rx.itemSelected.map { _ in () } let itemDeselectedTrigger = tableView.rx.itemDeselected.map { _ in () } let dataSourceUpdateTrigger = sections.map { _ in () } let tableViewReloadTrigger = tableView.rx.didReloadData.map { _ in () } // 合并所有触发源,确保任何状态变化都会触发检查 let stateCheckTrigger = Observable.merge( itemSelectedTrigger, itemDeselectedTrigger, dataSourceUpdateTrigger, tableViewReloadTrigger ) // 2. 创建「是否存在有效选中行」的Observable let hasValidSelectedRow = stateCheckTrigger .startWith(()) // 初始时触发一次检查 .map { [weak self] _ in guard let self = self else { return false } // 先获取当前选中的indexPath guard let selectedIndexPath = self.tableView.indexPathForSelectedRow else { return false } // 获取当前数据源的section数据,检查选中的indexPath是否有效 guard let dataSource = self.tableView.dataSource as? RxTableViewSectionedAnimatedDataSource<YourSectionType>, let currentSections = dataSource.sectionModels as? [YourSectionType] else { return false } // 检查section是否越界 guard selectedIndexPath.section < currentSections.count else { return false } // 检查row是否越界 let targetSection = currentSections[selectedIndexPath.section] return selectedIndexPath.row < targetSection.items.count } .distinctUntilChanged() // 避免重复发射相同状态,优化性能 // 3. 绑定到按钮的isHidden属性 // 注意:hasValidSelectedRow为true时按钮要显示,所以isHidden取反 hasValidSelectedRow .map { !$0 } .bind(to: actionButton1.rx.isHidden, actionButton2.rx.isHidden, actionButton3.rx.isHidden) .disposed(by: disposeBag)
为什么之前的方案失效?
方案一:监听indexPathForSelectedRow的问题
- 首先,你处理可选值的代码有误:
tableView.rx.observe(IndexPath?.self, "indexPathForSelectedRow")返回的是Observable<IndexPath?>,而非双重可选,所以map { ($0 as? IndexPath) == nil }是多余的,正确写法应该是map { $0 == nil }。 - 更关键的是:当数据源更新导致选中行被移除时,
indexPathForSelectedRow不会自动变为nil(TableView可能保留了之前的选中状态,但对应的行已经不存在),所以这个流不会发射新值,按钮也就不会隐藏。
方案二:订阅itemSelected和itemDeselected的问题
这个方案只覆盖了用户手动操作的场景,完全没有处理数据源更新导致选中行消失的情况——此时itemDeselected不会被触发,按钮会一直保持显示状态。
额外优化点
- 如果你的TableView支持多选,只需要把
indexPathForSelectedRow换成indexPathsForSelectedRows?.isEmpty == false即可,逻辑保持一致。 - 使用
distinctUntilChanged()可以避免不必要的UI刷新,提升性能。
内容的提问来源于stack exchange,提问作者Sweeper
相关产品推荐
相关产品推荐

