在 UItableview 单元格中重用计时器

Timer being reused in UItableview cell

我正在开发我的 UITableviewcell,其中有一个 UIView 供用户“点击”并启动计时器。我完全以编程方式创建我的 UItableview 单元格组件,并在 UITableviewcell class 而不是父视图控制器中添加计时器组件。

我遇到的问题是我的计时器对于每个 UITableviewcell 应该是不同的。用户应该能够在一个单元格中启动计时器并滚动浏览其他单元格到另一个单元格 start/stop。但是,当我“点击”我的第一个单元格时,计时器不仅在第一个单元格上启动,而且我还可以看到它在第三个单元格上启动。

我试图通过使用 PrepareForReuse 和使计时器无效来纠正此问题,但它也会导致第一个单元格的计时器无效。

我试图研究这个问题,但没有找到完全匹配的结果。谁能告诉我如何解决这个问题?

class ActiveExerciseTableViewCell: UITableViewCell, UITextFieldDelegate {
    
    static let tableviewidentifier = "activeExerciseTableViewCell"
    
    var tableviewContentViewTabBarHeight = CGFloat()
    
    var exerciseCellKeyboardHeight = CGFloat()
    
    var restTimer = Timer()
    var restTimeRemaining: Int = 180
    var isRestTimerRunning: Bool = false

    let activeExerciseTimerUIView: UIView = {
        let activeExerciseTimerUIView = UIView()
        activeExerciseTimerUIView.backgroundColor = .darkGray
        return activeExerciseTimerUIView
    }()
    
    let timerLabel: UILabel = {
        let timerLabel = UILabel()
        timerLabel.text = "180"
        timerLabel.textColor = .black
        timerLabel.adjustsFontSizeToFitWidth = true
        return timerLabel
    }()


    override func prepareForReuse() {
        super.prepareForReuse()
        
        if self.isRestTimerRunning == false {
            restTimer.invalidate()
            restTimeRemaining = 180
            timerLabel.text = prodTimeString(time: TimeInterval(restTimeRemaining))
        }

    }


func setUpActiveExerciseUIViewLayout(){
        
        timerLabel.translatesAutoresizingMaskIntoConstraints = false
        timerLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        timerLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: (contentView.frame.height-tableviewContentViewTabBarHeight)*0.55).isActive = true
        timerLabel.widthAnchor.constraint(equalToConstant: contentView.frame.width * 0.7).isActive = true
        timerLabel.heightAnchor.constraint(equalToConstant: 80).isActive = true
        timerLabel.font = .boldSystemFont(ofSize: 64)
        
        
        activeExerciseTimerUIView.translatesAutoresizingMaskIntoConstraints = false
        activeExerciseTimerUIView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        activeExerciseTimerUIView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: (contentView.frame.height-tableviewContentViewTabBarHeight)*0.25).isActive = true
        activeExerciseTimerUIView.widthAnchor.constraint(equalToConstant: 225).isActive = true
        activeExerciseTimerUIView.heightAnchor.constraint(equalToConstant: 225).isActive = true

        let timerStartGesture = UITapGestureRecognizer(target: self, action: #selector(playTapped))
        timerStartGesture.numberOfTapsRequired = 1
        
        activeExerciseTimerUIView.addGestureRecognizer(timerStartGesture)
        activeExerciseTimerUIView.isUserInteractionEnabled = true


    }


 @objc func playTapped(_ sender: Any) {
        
        if isRestTimerRunning == false {
            restTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(step), userInfo: nil, repeats: true)
            self.isRestTimerRunning = true
        }
      
    }
    
    
    @IBAction func pauseTapped(_ sender: Any) {
        restTimer.invalidate()
    }
    

    @IBAction func resetTapped(_ sender: Any) {
        restTimer.invalidate()
        restTimeRemaining = 180
        timerLabel.text = prodTimeString(time: TimeInterval(restTimeRemaining))
    }
    
    
    @objc func step() {
        if restTimeRemaining > 0 {
            restTimeRemaining -= 1
        } else {
            restTimer.invalidate()
            restTimeRemaining = 180
        }
        timerLabel.text = prodTimeString(time: TimeInterval(restTimeRemaining))
    }
    
    public func prodTimeString(time: TimeInterval) -> String {
        let Minutes = Int(time) / 60 % 60
        let Seconds = Int(time) % 60

        return String(format: "%02d:%02d", Minutes, Seconds)
    }

我发布了我自己的答案,问题已由

解决
  1. 正在创建一个单独的视图模型来存储计时器。
  2. 正在使用来自表视图委托的唯一“reuseID”初始化 UITableView 单元格。
SetViewModel

class SetViewModel {
    
    init(set: SetInformation) {
        self.set = set
        self.secondsRemaining = set.restTime ?? 0
        self.elapsedSeconds = 0
        self.isTimerRunning = false
        self.currentBackgroundDate = nil
        self.buttonPressCount = 0

    }
    
    var set: SetInformation
    var secondsRemaining: Int
    var elapsedSeconds: Int
    var isTimerRunning: Bool
    var currentBackgroundDate: Date?
    var buttonPressCount: Int
    
    weak var delegate: SetViewModelDelegate?
    
    // MARK: - Timer
    private var timer: Timer?
    
    func startTimer() {
        //stopTimer()
        isTimerRunning = true
        timer = .scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] timer in
            guard let self = self else {
                // If the model was discarded during the runtime of the timer
                timer.invalidate()
                return
            }
            
            if self.secondsRemaining > 0 && self.elapsedSeconds < (self.secondsRemaining + self.elapsedSeconds) {
                self.secondsRemaining -= 1
                self.elapsedSeconds += 1
            } else {
                timer.invalidate()
            }
            
            self.delegate?.setTimerUpdated(model: self)
            
        })
    }
    
    func stopTimer() {
        timer?.invalidate()
        isTimerRunning = false
        self.delegate?.setTimerUpdated(model: self)
    }

UItableView 委托


 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let reuseID = String(indexPath.row) + ActiveExerciseTableViewCell.tableviewidentifier
        
        activeExerciseTableView.register(ActiveExerciseTableViewCell.self, forCellReuseIdentifier: reuseID)
        
        let cell = activeExerciseTableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath) as! ActiveExerciseTableViewCell
        
        //configure set information for the tableview cell based on setviewmodel
        cell.configure(setViewModel: setViewModels[indexPath.row], currentHeartRate: userCurrentHeartRate ?? 00)
        
        cell.setDescriptionLabel.text = "Set \(String(indexPath.row + 1)) of \(activeExerciseList[activeDisplayedExericseRow].setInformation!.count)"
        
        cell.tableviewContentViewTabBarHeight = contentViewTabBarHeight
        cell.delegate = self

        return cell
    }