Swift:解决TableView滑动时无法实时reload数据的问题
解决TableView滑动时Timer停止刷新的问题
这个问题我做项目时也碰到过!核心原因是UITableView滑动时,RunLoop的运行模式会切换,导致你原来的Timer暂停触发了。
问题根源
iOS的RunLoop有多种运行模式,我们默认创建的Timer是绑定在DefaultRunLoopMode下的。当TableView开始滑动时,RunLoop会切换到UITrackingRunLoopMode(专门处理滑动交互的模式),这时候Default模式下的Timer就会暂时停止工作,直到滑动结束、RunLoop切回Default模式才会继续。
具体解决方案
下面给你几个实用的解决办法,按推荐程度排序:
1. 使用CADisplayLink代替Timer(最推荐)
CADisplayLink是和屏幕刷新率同步的定时器,默认就会加入RunLoop的CommonModes(包含Default和Tracking模式),滑动时完全不会停,而且更适合UI刷新场景(避免Timer和屏幕帧率不同步导致的视觉卡顿)。
示例代码:
var displayLink: CADisplayLink? override func viewDidLoad() { super.viewDidLoad() // 创建CADisplayLink displayLink = CADisplayLink(target: self, selector: #selector(updateCounterAndRefresh)) // 添加到RunLoop的common模式 displayLink?.add(to: .current, forMode: .common) } @objc func updateCounterAndRefresh() { // 这里更新你的整数数值 yourCounter += 1 // 只刷新可见的cell,比reloadData性能好 if let visibleIndexPaths = tableView.indexPathsForVisibleRows { tableView.reloadRows(at: visibleIndexPaths, with: .none) } } deinit { // 记得销毁,避免内存泄漏 displayLink?.invalidate() }
2. 把Timer加入RunLoop的CommonModes
如果你不想换CADisplayLink,也可以修改原Timer的RunLoop模式,让它在滑动时也能触发:
示例代码:
var timer: Timer? override func viewDidLoad() { super.viewDidLoad() timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateCounterAndRefresh), userInfo: nil, repeats: true) // 关键:把Timer加到CommonModes RunLoop.current.add(timer!, forMode: .common) } @objc func updateCounterAndRefresh() { yourCounter += 1 // 同样推荐只刷新可见cell if let visibleIndexPaths = tableView.indexPathsForVisibleRows { tableView.reloadRows(at: visibleIndexPaths, with: .none) } } deinit { timer?.invalidate() }
3. 优化刷新逻辑(必做)
不管用哪种定时器,都不要每次调用tableView.reloadData()——滑动时频繁刷新整个TableView不仅性能差,还可能导致滑动卡顿。只刷新当前可见的cell就足够了,代码里已经体现了这一点。
额外注意事项
- 一定要在控制器销毁时(
deinit)调用invalidate()销毁定时器,避免循环引用和内存泄漏; - 如果用Selector回调,记得在方法里用
weak self(比如Swift里可以写成@objc func updateCounterAndRefresh() { [weak self] guard let self = self else { return } ... }),防止控制器无法被释放。
内容的提问来源于stack exchange,提问作者Alexander Wad
相关产品推荐
相关产品推荐

