I am working on my UITableviewcell where I have a UIView for user to "tap" and start timer. I am creating my UItableview cell components completely programmatically and adding timer components within UITableviewcell class instead of the parent view controller.
The problem I am having is that my timer is supposed to be distinct for each UITableviewcell. User should be able to start timer in one cell and scroll through other cells to start/stop another. However, when I "tap" on my first cell, the timer gets started not only on the first one, but I can also see that it is starting on the 3rd cell.
I tried to rectify this issue by using PrepareForReuse and invalidate timer, but it also result in the 1st cell's timer to be invalidated.
I tried to research on this issue, but have found no exact match. Could anyone kindly advise how I can resolve this issue?
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)
}
Iam posting my own answer, issue had been resolved the issue by
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 Delegate
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
}