Search code examples
swifttimeruicollectionviewlabeluicollectionviewcell

Having collectionViewCell.label.text Change Every Second with Timer


I am currently learning to code swift apps and am trying to do some projects on my own. My current personal challenge is to do a countdown timer app. The concept is simple: the user enters a date, from a UIDatePicker, and the app shows the time remaining until his list of various events (uses user default values to keep the events in memory). All the said events are shown in a collection view (see below for screen shots).

I ran into something too complicated for my actual skillset and I thought you guys probably would have the answer, or at least a few suggestions! Here is my issue: I'd like for the time remaining between today and the event to decrease every second, and to be shown through a label inside a collectionViewCell. I found a very simple way to do so, with a timer, but implementing the said solution with a collectionViewCell is giving me quite the headache.

Here are the code excerpts I'd like to share, and hopefully it's enough:

@objc func UpdateTimeLabel(index: Int) -> String {
    
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
    dateFormatter.timeZone = userCalendar.timeZone
    
    let date = dateFormatter.date(from: UserDefaults.standard.array(forKey: "dates")![index] as! String)!
    
    let timeLeft = userCalendar.dateComponents([.day, .hour, .minute, .second], from: currentDate, to: date)

    return "\(timeLeft.day!) days \(timeLeft.hour!) hours \(timeLeft.minute!) minutes \(timeLeft.second!) seconds"
    
}

That's my function I'd like to fire every second. It is currently made in such a way that its property, called index, is the indexPath.row of the collectionViewCell. It references to an array of event dates stored as UserDefaults. There's as many cells in the collectionView as there is events in the array.

See below the way I implemented it inside the collectionView forItemAt function:

   func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
            
        // Configure the state of the cell based on the propertires of the card if reprensents
        let cardCell = cell as? EventCollectionViewCell
        
        // UI Configuration
        cardCell?.eventTitleLabel.text = ((UserDefaults.standard.array(forKey: "events")![indexPath.row]) as! String)
        
        cardCell?.eventDateLabel.text = ("Event on: " + "\((UserDefaults.standard.array(forKey: "dates")![indexPath.row]) as! String)")

        cardCell?.countdownLabel.text = UpdateTimeLabel(index: indexPath.row)
    
        cardCell?.eventView.layer.cornerRadius = 10
          
    }

The actual result is that every cell shows the time remaining between today and the event. That's already more than I thought I could do by myself!!!

Results of actual code

Where I think one of you can probably step-in is by helping me answering this question: Where should a timer, looking something like the code below, be placed in order for the cardCell.countdownLabel.text to be updated every second?

timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(UpdateTimeLabel), userInfo: nil, repeats: true)

I tried every ways I can think of and am now at a road block. I still have many more things to fix in my project, so I'm not completely stopped yet. If you need more code lines and/or precisions, let me know and I'll provide you with anything you deem helpful.

Thank you so much for taking the time to review my question,

Louis G


Solution

  • Add this method UpdateTimeLabel method into the EventCollectionViewCell and modify method like this

    @objc func UpdateTimeLabel() {
        let userCalendar = Calendar(identifier: .indian)
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
        dateFormatter.timeZone = userCalendar.timeZone
        
        let date = dateFormatter.date(from: UserDefaults.standard.array(forKey: "dates")![index] as! String)!
        
        let timeLeft = userCalendar.dateComponents([.day, .hour, .minute, .second], from: Date(), to: date)
    
        countdownLabel.text = "\(timeLeft.day!) days \(timeLeft.hour!) hours \(timeLeft.minute!) minutes \(timeLeft.second!) seconds"
        
    }
    

    After doing this add a property index in EventCollectionViewCell and on didSet of index fire timer in EventCollectionViewCell eg:

    var index: Int {
        didSet {
            let timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(UpdateTimeLabel), userInfo: nil, repeats: true)
            timer.fire()
        }
    }
    

    Replace

    cardCell?.countdownLabel.text = UpdateTimeLabel(index: indexPath.row)
    

    with

    cardCell?.index = indexPath.row