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

如何解决BLE回调场景下Table View滚动持有时的Cell闪烁问题?

解决BLE回调触发时TableView Cell滚动按住闪烁的问题

咱们先来理清楚问题的根源:你用定时器刷新的时候,系统默认会把定时器加入RunLoop的.commonModes,当TableView处于拖动或者减速滚动状态时,RunLoop会切换到.trackingRunLoopMode,这时候commonMode的定时器就会暂停,所以不会触发刷新,自然没闪烁。但BLE的回调不一样,它一般是在后台处理完数据后直接切到主线程执行刷新,不管RunLoop当前是什么模式,所以哪怕你按住滚动TableView,回调还是会持续触发reloadRows,频繁的刷新操作就导致了Cell闪烁。

接下来给你几个可行的解决方案,结合你的代码来改:

方案一:判断滚动状态,滚动时暂存更新,停止后再刷新

这个思路是:先保证数据源始终是最新的,然后判断TableView是否在滚动,如果在滚动就标记需要刷新,等滚动停止后再统一更新UI。

步骤1:添加一个标记变量

在你的ViewController里加一个布尔值,用来记录是否需要在滚动停止后刷新:

private var needsReloadAfterScroll = false

步骤2:修改BLE回调的刷新方法

把原来的刷新逻辑拆成更新数据源和条件刷新UI两部分:

@objc func reloadTableViewCellsOnBleScan(_ name: String) {
    // 第一步:先更新数据源,确保数据是最新的
    var targetIndex: IndexPath?
    if let index = myArray.firstIndex(where: { $0 == name }) {
        myArray[index] = name
        targetIndex = IndexPath(row: index, section: 0)
    } else {
        myArray.append(name)
        needsReloadAfterScroll = true
    }
    
    // 第二步:判断TableView是否处于静止状态,只有静止时才立即刷新
    if !tableViewRx.isDragging && !tableViewRx.isDecelerating {
        if let indexPath = targetIndex {
            tableViewRx.reloadRows(at: [indexPath], with: .none)
        } else {
            reloadTableView()
        }
        needsReloadAfterScroll = false // 刷新后重置标记
    } else {
        // 如果正在滚动,标记需要刷新
        if targetIndex != nil {
            needsReloadAfterScroll = true
        }
    }
}

步骤3:实现TableView滚动停止的代理方法

让你的ViewController遵守UITableViewDelegate(如果还没遵守的话),然后添加以下代理方法,处理滚动停止后的刷新:

extension YourViewController: UITableViewDelegate {
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        // 如果拖动后直接停止,没有减速,就立即刷新
        if !decelerate && needsReloadAfterScroll {
            updateTableViewAfterScroll()
        }
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        // 滚动减速停止后刷新
        if needsReloadAfterScroll {
            updateTableViewAfterScroll()
        }
    }
    
    // 封装一个统一的刷新方法
    private func updateTableViewAfterScroll() {
        // 这里可以选择整体reload,或者更精准地刷新变化的行
        // 如果要精准刷新,你可以额外记录变化的indexPath数组,这里为了简单用reloadData
        tableViewRx.reloadData()
        needsReloadAfterScroll = false
    }
}

方案二:给BLE回调的刷新加防抖(可选)

因为BLE允许重复扫描的话,可能会在短时间内多次回调同一个设备的信息,频繁刷新同一个Cell也会导致闪烁。你可以加一个防抖机制,比如用GCD的定时器,确保100ms内只刷新一次:

private var debounceTimer: Timer?

@objc func reloadTableViewCellsOnBleScan(_ name: String) {
    // 先更新数据源
    var targetIndex: IndexPath?
    if let index = myArray.firstIndex(where: { $0 == name }) {
        myArray[index] = name
        targetIndex = IndexPath(row: index, section: 0)
    } else {
        myArray.append(name)
        needsReloadAfterScroll = true
    }
    
    // 取消之前的定时器
    debounceTimer?.invalidate()
    // 新建定时器,100ms后执行刷新
    debounceTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { [weak self] _ in
        guard let self = self else { return }
        if !self.tableViewRx.isDragging && !self.tableViewRx.isDecelerating {
            if let indexPath = targetIndex {
                self.tableViewRx.reloadRows(at: [indexPath], with: .none)
            } else {
                self.reloadTableView()
            }
            self.needsReloadAfterScroll = false
        } else {
            self.needsReloadAfterScroll = true
        }
    }
}

这个方案可以和方案一结合使用,进一步减少不必要的刷新操作,避免闪烁。

为什么定时器场景没问题?

再补充一下你提到的定时器场景的原理:Timer.scheduledTimer默认会把定时器加入RunLoop的.commonModes,当TableView滚动时,RunLoop会切换到.trackingRunLoopMode,此时commonMode的定时器会暂停,直到滚动停止后才继续触发,所以不会有闪烁的问题。而BLE回调是直接在主线程执行,不受RunLoop模式的影响,所以滚动时还是会触发刷新。

内容的提问来源于stack exchange,提问作者yuvaraju

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:04:21