1
votes

I have a collectionView that represents a span of time. Each cell is a month. In each cell there is a horizontal stack view where each view represents a day.

The full project is here if you wanna try it out: https://github.com/AlexMarshall12/iOS-timeline

In my ViewController, I generate a random array of dates. Each time a cell is called in cellForItemAt, a custom function finds the dates in the array which are in the cell month.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MonthCollectionViewCell", for: indexPath) as! MonthCollectionViewCell
    cell.backgroundColor = UIColor.gray
    let firstDate = dates.first
    let index = indexPath.item
    let monthDate = Calendar.current.date(byAdding: .month, value: index, to: firstDate as! Date)
    let monthInt = Calendar.current.component(.month, from: monthDate!)
    let yearInt = Calendar.current.component(.year, from: monthDate!)
    cell.monthLabel.text = String(monthInt)+" "+String(yearInt)
    cell.year = yearInt
    cell.month = monthInt
    let monthDates = dates(self.dates as! [Date], withinMonth: monthInt, withinYear: yearInt)
    cell.colorViews(monthDates:monthDates)
    return cell
}
    func dates(_ dates: [Date], withinMonth month: Int, withinYear year: Int) -> [Date] {
    let calendar = Calendar.current
    let components: Set<Calendar.Component> = [.month,.year]
    let filtered = dates.filter { (date) -> Bool in
        let monthAndYear = calendar.dateComponents(components, from: date)
        return (monthAndYear.month == month && monthAndYear.year == year)
    }
    return filtered
}

These are passed to the custom cell class and it finds the subviews which represent those days and colors them like so:

class MonthCollectionViewCell: UICollectionViewCell {

    @IBOutlet weak var monthLabel: UILabel!
    @IBOutlet weak var stackView: UIStackView!
    var year: Int?
    var month: Int?

    override func awakeFromNib() {
        super.awakeFromNib()
        for _ in 0...30 {       //for the 31 days in a month...
            let tick = UIView()
            self.stackView?.addArrangedSubview(tick)
        }
    }

    func colorViews(monthDates: [Date]){
        for date in monthDates {
            let dayIndex = Calendar.current.component(.day, from: date)
            let tick = stackView.arrangedSubviews[dayIndex]
            tick.backgroundColor = UIColor.red
        }
    }
}

I believe this should give me the desired affect. For instance if there are 3 dates generated across 2 years, it would create 3 stripes across 2 years worth of month cells. However, I find that many more than 3 stripes are rendered, almost like its duplicating the subview across many cells.

Here is what it looks like now: https://imgur.com/a/ZNSmIS8. Note this was with 3 dates. 5 stripes appear. It seems that scrolling back and forth adds stripes as though cellForItemAt is being called on the wrong cell? Im not exactly sure.

Any advice on what I am doing wrong in this implementation would be awesome.

Edit: I have added this method to my custom cell class:

override func prepareForReuse() {
    super.prepareForReuse()
    for each_view in self.subviews {
        print("Cleared!")
        each_view.backgroundColor = UIColor.clear
    }
}

The same issue is still occurring especially with a really long time range and/or fast scrolling

1

1 Answers

2
votes

It doesn't look like you're actually resetting the content of the cells that are being returned to you by dequeueReusableCell.

dequeueReusableCell provides you with a cell that may have been already used in order to optimise for performance. The documentation for the method notes:

If an existing cell was available for reuse, this method calls the cell’s prepareForReuse() method instead.

If you check out the documentation for prepareForReuse(), you will find that you get a chance to reset the properties of the cell so that you can reuse the cell.

If you reset the cell, you will probably find that it starts behaving the way you expect it to.