【问题标题】:Timer being reused in UItableview cell在 UItableview 单元中重用计时器
【发布时间】:2021-10-28 16:23:59
【问题描述】:

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

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

我尝试通过使用 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)
    }

【问题讨论】:

  • 我建议您不要将 any Timer 逻辑存储在您的单元格中 - 这些应该仅用于 UI。相反,创建一个单独的数据模型来存储您的计时器和索引,并编写表 UI 以反映计时器的状态。
  • 另一件事是,当单元被重用时,计时器可能会失效。这将在您滚动时发生。计时器按钮上的 tsp 应在表格视图控制器中处理,每个单元格有一个计时器。你可以是一个数组或一个定时器字典。
  • 感谢 cmets,我将创建一个单独的数据模型并对其进行处理,我还找到了一些示例解决方案 - stackoverflow.com/questions/61750038/… 如果我设法更新我的代码并发布我的解决方案跨度>

标签: ios swift uitableview timer prepareforreuse


【解决方案1】:

我正在发布我自己的答案,问题已解决

  1. 创建一个单独的视图模型来存储计时器。
  2. 使用来自 tableview 委托的唯一“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
    }

【讨论】:

    猜你喜欢
    • 2018-11-27
    • 1970-01-01
    • 2017-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多