You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取表示TableView是否存在选中行的Observable<Bool>?

解决RxDataSources下TableView选中行状态联动按钮显示的问题

我明白你遇到的痛点了——用RxDataSources驱动的TableView,要完美联动按钮的显示隐藏,得覆盖用户手动选中/取消和数据源更新导致选中行消失这两种核心场景,之前的两种方案都只覆盖了部分情况。下面给你一个完整的解决方案:

核心思路

我们需要创建一个能实时反映「是否存在有效选中行」的Observable<Bool>,这个Observable要监听所有可能改变选中状态的事件:

  1. 用户手动选中行
  2. 用户手动取消选中行
  3. 数据源更新(可能导致原有选中行被移除)
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:50:36