The best way of adding countdown label with timer

2019-04-17 07:59发布

问题:

I have a list of items which are presenting to the user in UICollectionView. These items have a countdown label to show the remaining time that the item is available. I used a Timer in UICollectionViewCell to show the remaining time like:

OperationQueue.main.addOperation {
        var remaingTimeInterval = self.calculateRemainigTime(remainingTime: remainingTime)
        if remaingTimeInterval > 0 {
            self.timerLabel.isHidden = false
            self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] (_) in
                let hours = Int(remaingTimeInterval) / 3600
                let minutes = Int(remaingTimeInterval) / 60 % 60
                let seconds = Int(remaingTimeInterval) % 60
                self?.timerLabel?.text = String(format:"%02i:%02i:%02i", hours, minutes, seconds)
                remaingTimeInterval -= 1
            })
        } else {
            self.timer?.invalidate()
            self.timerLabel.isHidden = true
        }
    }

and that's how I calculate the remaining time based on the given Date:

//Calculating remaining time based on the item endDate and currentDAte
    func calculateRemainigTime(remainingTime: String) -> Int {
        let prizeRemainingTime = Helper.stringToDate(remainingTime)
        let prizeRemainingTimeInterval = Int(prizeRemainingTime.timeIntervalSince1970)
        let currentTimeInterval = Int(Date().timeIntervalSince1970)
        return prizeRemainingTimeInterval - currentTimeInterval
    }

Everything works fine till the cell is being reused, after that the countdown numbers are not correct anymore.

Is this a correct way to show the countdown in UICollectionViewCell or there is a better solution.

Can anyone help me to find a way through this?

回答1:

  • Move the timer logic to the data model.
  • Instead of target/action use the block-based API of Timer.
  • In cellForRow pass the timer callback block to the cell.
  • When the timer fires the code in the callback block can update the UI in the cell.


回答2:

Coding from accepted answer guideline, hope you like it.

MyViewController

protocol MyViewControllerDelegate : class {
    func updateTimer(_ timeString: String)
}

class MyViewController: UIViewController {

    weak var timerDetegate: MyViewControllerDelegate?

    var timer = Timer()
    var time = 0

    fun ViewDidLoad() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
            self.time += 1
            self.timerDetegate?.updateTimer("time \(self.time)")
        })
    }

    ...

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableViewdequeueReusableCell(withIdentifier: "MeCell")
        vc.timerDetegate = cell
        return cell
    }

    ...

}

MyTableViewCell

class MyTableViewCell : UITableViewCell, MyViewControllerDelegate {

    ...

    func updateTimer(_ timeString: String) {
        lblTimeLeft.text = timeString
    }

}


回答3:

I had implemented this simpler way (without caring about the performance/latency)

in ..cellForItemAt.. method, I call

cell.localEndTime = yourEndTimeInterval
cell.startTimerProgress()

In collection view cell, I added a method which starts the progress:

    var localEndTime: TimeInterval = 0 //set value from model
    var timer:Timer?
    func timerIsRunning(timer: Timer){
        let diff = localEndTime  - Date().timeIntervalSince1970
        if diff  > 0 {
            self.showProgress()
            return
        }
        self.stopProgress()


    }
    func showProgress(){
        let endDate = Date(timeIntervalSince1970: localEndTime)
        let nowDate = Date()
        let components = Calendar.current.dateComponents(Set([.day, .hour, .minute, .second]), from: nowDate, to: endDate)
        self?.timerLabel?.text = String(format:"%02i:%02i:%02i", components.hour!, components.minute!, components.second!)

    }

    func stopProgress(){
         self?.timerLabel?.text = String(format:"%02i:%02i:%02i", 0, 0, 0)
        self.timer?.invalidate()
        self.timer = nil
    }

    func startTimerProgress() {
        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerIsRunning(timer:)), userInfo: nil, repeats: true)
        self.timer?.fire()
    }