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

UITableViewCell内置Timer出现复用冲突,如何实现各单元格独立计时?

问题根因

你遇到的多单元格计时器同步启动、prepareForReuse误杀运行中计时器的问题,核心是两个设计错误:

  • 把计时器运行状态、剩余时长这类行级持久状态直接存在了可复用的UITableViewCell实例中:UITableView会自动复用移出屏幕的Cell来渲染新的行,你启动了计时器的第1行Cell滚出屏幕后,会被系统分配给第3行复用,状态直接串用
  • 原有prepareForReuse逻辑存在漏洞:只有isRestTimerRunning为false时才重置状态,反过来如果Cell被复用时原计时器正在运行,你根本没有清理旧计时器,直接带着前一行的状态渲染新行
修复步骤

1. 把计时器状态抽到外部数据源

不要在Cell内部存储行的独立状态,先给父视图控制器的数据源模型加三个属性:

// 你的运动项模型示例
struct ExerciseModel {
    // 原有其他业务属性保留
    var isRestTimerRunning: Bool = false
    var restTimeRemaining: Int = 180
}

给Cell加状态回调闭包,所有计时器状态变更同步给外部数据源:

class ActiveExerciseTableViewCell: UITableViewCell, UITextFieldDelegate {
    // 原有属性保留,新增回调
    var onTimerStateUpdated: ((_ isRunning: Bool, _ remainingTime: Int) -> Void)?
    // 把原有restTimer改成可选类型,避免初始空Timer调用invalidate出异常
    var restTimer: Timer?
    // 删除Cell内部存储的isRestTimerRunning、restTimeRemaining属性,状态全从外部传入
}

2. 新增Cell配置方法

每次Cell被渲染到屏幕上时,用对应行的数据源状态重置Cell内容和计时器:

// 写在Cell类内部
func config(with model: ExerciseModel) {
    // 先清理旧计时器,完全切断和前一行的关联
    restTimer?.invalidate()
    restTimer = nil
    
    // 按当前行的状态更新UI
    timerLabel.text = prodTimeString(time: TimeInterval(model.restTimeRemaining))
    // 如果当前行的计时器处于运行状态,直接重启计时器
    if model.isRestTimerRunning {
        restTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(step), userInfo: nil, repeats: true)
    }
}

3. 修改计时器事件逻辑,所有状态变更同步到外部

@objc func playTapped(_ sender: Any) {
    guard restTimer == nil else { return }
    restTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(step), userInfo: nil, repeats: true)
    onTimerStateUpdated?(true, restTimeRemaining)
}

@IBAction func pauseTapped(_ sender: Any) {
    restTimer?.invalidate()
    restTimer = nil
    onTimerStateUpdated?(false, restTimeRemaining)
}

@IBAction func resetTapped(_ sender: Any) {
    restTimer?.invalidate()
    restTimer = nil
    restTimeRemaining = 180
    timerLabel.text = prodTimeString(time: TimeInterval(restTimeRemaining))
    onTimerStateUpdated?(false, 180)
}

@objc func step() {
    if restTimeRemaining > 0 {
        restTimeRemaining -= 1
    } else {
        restTimer?.invalidate()
        restTimer = nil
        restTimeRemaining = 180
    }
    timerLabel.text = prodTimeString(time: TimeInterval(restTimeRemaining))
    // 每秒同步最新状态给外部数据源
    onTimerStateUpdated?(restTimer != nil, restTimeRemaining)
}

4. 父视图控制器实现回调更新数据源

在cellForRowAt方法里配置Cell、实现状态回调:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: ActiveExerciseTableViewCell.tableviewidentifier, for: indexPath) as! ActiveExerciseTableViewCell
    let currentModel = dataArray[indexPath.row]
    cell.config(with: currentModel)
    // 接收Cell的状态变更,更新对应行的数据源
    cell.onTimerStateUpdated = { [weak self] isRunning, remainingTime in
        guard let self = self else { return }
        self.dataArray[indexPath.row].isRestTimerRunning = isRunning
        self.dataArray[indexPath.row].restTimeRemaining = remainingTime
    }
    return cell
}

5. 移除原有的错误prepareForReuse逻辑

你原来写的prepareForReuse代码完全不需要保留,所有重置逻辑已经在config方法里处理完成。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 17:06:03