Table View Cell中倒计时器滚动后显示数值异常问题
这个问题我太熟悉了——UITableViewCell复用+定时器简直是iOS开发里的经典踩坑场景!我来帮你拆解问题根源,一步步搞定它。
问题根源分析
快速滚动时倒计时数值异常,本质是cell复用导致的定时器与数据绑定混乱:
- 滚出屏幕的cell被复用时,旧定时器可能没完全停止,仍在回调更新已复用cell的UI;
- 新数据的定时器创建时机滞后,导致短暂显示旧cell的倒计时数值;
- 滚动时RunLoop模式切换,定时器可能暂停,滚回后才恢复更新,加剧数值跳变。
具体修复方案
1. 彻底清理复用前的状态,避免残留旧数据
在prepareForReuse中不仅要销毁定时器,还要重置UI状态,防止复用后短暂显示旧值:
override func prepareForReuse() { super.prepareForReuse() // 销毁旧定时器 timer?.invalidate() timer = nil // 重置倒计时文本和隐藏状态 timeLabel.text = "" timeLabel.isHidden = false // 其他需要重置的UI元素也同步处理 }
2. 重构定时器逻辑,强绑定截止日期
修改deadlineDate的didSet,确保每次更新日期时先销毁旧定时器,再根据新日期创建新定时器,同时避免循环引用:
var deadlineDate: Date? { didSet { // 先销毁旧定时器,避免混乱 timer?.invalidate() timer = nil // 立即更新一次UI updateTimeLabel() // 只有当截止日期有效且未过期时,才启动定时器 guard let deadline = deadlineDate, Date() < deadline else { timeLabel.text = "已截止" return } // 使用weak self避免循环引用(定时器持有cell,cell持有定时器) timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in self?.updateTimeLabel() } // 将定时器加入common模式RunLoop,确保滚动时也能正常触发 if let timer = timer { RunLoop.current.add(timer, forMode: .common) } } } func updateTimeLabel() { guard let deadline = deadlineDate else { timeLabel.text = "" return } let now = Date() if now >= deadline { timeLabel.text = "已截止" timer?.invalidate() timer = nil return } // 计算时分秒差值 let components = calendar.dateComponents([.hour, .minute, .second], from: now, to: deadline) if let hour = components.hour, let minute = components.minute, let second = components.second { timeLabel.text = String(format: "%02d:%02d:%02d", hour, minute, second) } else { timeLabel.text = "" } }
3. 确保configure方法中数据与UI同步
在configure方法的最后一步设置deadlineDate,保证所有UI元素更新完成后,再触发倒计时逻辑:
func configure(for purchase: Purchase) { purchaseSubjectLabel.text = purchase.subject startingPriceLabel.text = purchase.NMC stageLabel.text = purchase.stage fzImageView.image = purchase.fedLaw.contains("44") ? #imageLiteral(resourceName: "FZ44") : #imageLiteral(resourceName: "FZ223") timeLabel.isHidden = purchase.stage == "Работа комиссии" warningImageView.image = purchase.warningImage // 最后设置截止日期,触发倒计时逻辑 deadlineDate = purchase.deadline // 假设Purchase模型包含deadline字段 }
4. 进阶优化:全局统一管理定时器(大量cell场景)
如果需要显示几十上百个倒计时,每个cell一个定时器会浪费资源,建议用全局定时器统一更新可见cell:
在ViewController中:
var globalCountdownTimer: Timer? override func viewDidLoad() { super.viewDidLoad() // 启动全局定时器,每秒更新一次可见cell globalCountdownTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in self?.updateVisibleCountdownCells() } RunLoop.current.add(globalCountdownTimer!, forMode: .common) } func updateVisibleCountdownCells() { guard let visibleCells = tableView.visibleCells as? [YourCustomCellClass] else { return } for cell in visibleCells { // 仅更新需要显示倒计时的cell if !cell.timeLabel.isHidden { cell.updateTimeLabel() } } } // 页面销毁时销毁全局定时器 deinit { globalCountdownTimer?.invalidate() globalCountdownTimer = nil }
同时修改CustomCell,移除自身的定时器,只保留deadlineDate和updateTimeLabel方法即可。
内容的提问来源于stack exchange,提问作者Gregory Kovler
相关产品推荐
相关产品推荐

