在 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)
}
我发布了我自己的答案,问题已由
解决
- 正在创建一个单独的视图模型来存储计时器。
- 正在使用来自表视图委托的唯一“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
}
我正在开发我的 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)
}
我发布了我自己的答案,问题已由
解决- 正在创建一个单独的视图模型来存储计时器。
- 正在使用来自表视图委托的唯一“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
}