Search code examples
iosswifttimeruicollectionviewuicollectionviewcell

CollectionView cell's timers get mixed up when collectionview refreshes


I have a collectionView that contains timers in each cell. When the app starts all the timers work fine, however, when I refresh the collection view all the timers get mixed up and are all over the place. I am pretty sure it has something to do with the previous timers not closing.

Code for collection view:

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "contestsCell", for: indexPath) as! ContestsCollectionViewCell
    cell.nameLabel.text = listingArray[indexPath.row].name


    let arr = [listingArray[indexPath.row].expiryDate, cell.expiryDateLabel] as [AnyObject]

    timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCounting), userInfo: arr, repeats: true)

    return cell
}

Code for timer function:

@objc func updateCounting(timer:Timer){
    // here we set the current date

    var dateArray: [AnyObject] = timer.userInfo as! [AnyObject]

    let itemDate = dateArray[0] as! String
    var dateList = itemDate.components(separatedBy: "-")

    let dateLabel = dateArray[1] as! UILabel

    let date = NSDate()
    let calendar = Calendar.current

    let components = calendar.dateComponents([.hour, .minute, .month, .year, .day, .second], from: date as Date)

    let currentDate = calendar.date(from: components)

    let userCalendar = Calendar.current

    // here we set the due date. When the timer is supposed to finish
    let competitionDate = NSDateComponents()
    competitionDate.year = Int(dateList[0])!
    competitionDate.month = Int(dateList[1])!
    competitionDate.day = Int(dateList[2])!
    competitionDate.hour = 00
    competitionDate.minute = 00
    competitionDate.second = 00
    let competitionDay = userCalendar.date(from: competitionDate as DateComponents)!

    //here we change the seconds to hours,minutes and days
    let CompetitionDayDifference = calendar.dateComponents([.day, .hour, .minute, .second], from: currentDate!, to: competitionDay)


    //finally, here we set the variable to our remaining time
    let daysLeft = CompetitionDayDifference.day
    let hoursLeft = CompetitionDayDifference.hour
    let minutesLeft = CompetitionDayDifference.minute
    let secondsLeft = CompetitionDayDifference.second

    if competitionDay > currentDate as! Date {
        //Set countdown label text
        dateLabel.text = "\(daysLeft ?? 0): \(hoursLeft ?? 0): \(minutesLeft ?? 0): \(secondsLeft ?? 0)"
    }

}

code for refreshing collectionView

@objc func loadData() {

    retrieveContests()
    listingArray.removeAll()
    self.refresher.endRefreshing()
}

Solution

  • I think your problem has to do with the fact that you dequeue your cells. Since the principle behind this is that existing cell objects are being reused by iOS that might lead to some problems.

    E.g. in the method you create cell1 with countdown1 and a timer and the same with cell2 and countdown2 (+timer). Once you reload your collectionView cell1 and cell2 will be reused, but now cell1 will have a different index path and is therefor used for countdown2 and also a new timer is created as well.

    This probably leads to the problem that your timers are getting mixed up in the process and as far as I can see in your code you are also never stopping any timers, so your assumption is correct I would say. Every time you reload your collection view, your are creating more timers.

    My suggestion to solve this would be: Create a property on each cell that contains the Date-object which you use to calculate the countdown. Then just use a single timer which notifies all cells to update their content on their own. Maybe you could use NotificationCenter for this. This (or a similar approach) should help you and avoid the whole problem with maintaining all these timers.