Wednesday, 17 October 2018

How to reload a single cell of a collectionView in the willDisplayCell callback?

I have a collectionView that I am making a timeline of sorts out of. There are many thin cells in a horizontal collectionView - each cell represents 1 day. Here is the project code: https://github.com/AlexMarshall12/singleDayTimeline.git

Here is the viewController:

let cellIdentifier = "DayCollectionViewCell"
class ViewController: UIViewController, UICollectionViewDataSource,UICollectionViewDelegate {

    @IBOutlet weak var button: UIButton!
    var dates = [Date?]()
    var startDate: Date?
    var endDate: Date?
    private var selectedIndexPath: IndexPath?

    @IBOutlet weak var daysCollectionView: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()
        daysCollectionView.register(UINib.init(nibName: "DayCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: cellIdentifier)

        let allDates = Helper.generateRandomDate(daysBack: 900, numberOf: 10)
        self.dates = allDates.sorted(by: {
            $0!.compare($1!) == .orderedAscending
        })
        self.startDate = Calendar.current.startOfDay(for: dates.first as! Date)

        self.endDate = dates.last!
        self.dates = Array(dates.prefix(upTo: 1))
        daysCollectionView.delegate = self
        daysCollectionView.dataSource = self
    }

    var onceOnly = false

    internal func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        if !onceOnly {
            //let lastDateIndexPath = IndexPath(row: dates.count - 1,section: 0)
            let lastDate = dates.last
            let lastDayIndex = lastDate!?.interval(ofComponent: .day, fromDate: startDate!)
            let lastDayCellIndexPath = IndexPath(row: lastDayIndex!, section: 0)
            self.daysCollectionView.scrollToItem(at: lastDayCellIndexPath, at: .left, animated: false)
            self.selectedIndexPath = lastDayCellIndexPath
            self.daysCollectionView.reloadItems(at: [lastDayCellIndexPath])

            onceOnly = true
        }
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        let days = self.endDate!.days(from: self.startDate!)
        if days <= 150 {
            return 150
        } else {
            print(days,"days")
            return days + 1
        }
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = daysCollectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! DayCollectionViewCell

        let cellDate = Calendar.current.date(byAdding: .day, value: indexPath.item, to: self.startDate!)

        if let selectedRow = selectedIndexPath {
            cell.reloadCell(selectedRow==indexPath)
        } else {
            cell.reloadCell(false)
        }

        if Calendar.current.component(.day, from: cellDate!) == 15 {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "MMM"
            let monthString = dateFormatter.string(from: cellDate!)
            cell.drawMonth(month: monthString)
        }
        if Calendar.current.component(.day, from: cellDate!) == 1 && Calendar.current.component(.month, from: cellDate!) == 1 {
            print("drawYEAR")
            cell.drawYear(year:Calendar.current.component(.year, from: cellDate!))
        }
        if self.dates.contains(where: { Calendar.current.isDate(cellDate!, inSameDayAs: $0!) }) {
            print("same")
            cell.backgroundColor = UIColor.red
        }
        //cell.backgroundColor = UIColor.blue
        return cell
    }

    @IBAction func buttonPressed(_ sender: Any) {

        let randomIndex = Int(arc4random_uniform(UInt32(self.dates.count)))
        let randomDate = self.dates[randomIndex]
        let daysFrom = randomDate?.days(from: self.startDate!)
        let indexPath = IndexPath(row: daysFrom!, section: 0)
        self.selectedIndexPath = indexPath;

        //daysCollectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredHorizontally)
        daysCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
        daysCollectionView.reloadData()
    }

}

Note that when the buttonPressed function is Called, the "selectedIndexPath" changes. Then the next time cellForItemAt is called, it does cell.reloadCell and if the indexPath is this selectedIndexPath, it unhides the arrowImageView (see below)

class DayCollectionViewCell: UICollectionViewCell {

    ...
    func reloadCell(_ isSelected:Bool){
        arrowImage.isHidden = !isSelected
    }

}

This works when the button is pressed, however I am trying to make it work in the willDisplayCell callback. You can see that in this callback I basically just get the latest (most recent) date and scroll to it and then set SelectedIndexPath to that indexPath. Now I need a way to make the arrow draw on that cell.

Here is what it looks like now: enter image description here

As you can see it doesn't show the arrow UNTIL you scroll around. I think what is happening is that when you scroll around enough cellForItemAt gets called which we know works. But How do I get willDisplayCell to do the same thing successfully and thus have the arrow load from initial startup?



from How to reload a single cell of a collectionView in the willDisplayCell callback?

No comments:

Post a Comment