Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DiffableDataSource: Snapshot Doesn't reload Headers & footers

I am using UICollectionViewDiffableDataSource for UICollectionView to display content in multiple sections.

I am using Collection View Compositional Layout and Diffable Datasources link which was introduced at WWDC'19 to render the Multiple Section Layout of UICollectionView

I have a simple setup, The Header for each section shows number of items in that section, and Footer shows the summary of all items of the section.

section 1 Header --> January 2020 - 5 Trips
section 1 item 1 --> Trip 1
section 1 item 2 --> Trip 2
section 1 item 3 --> Trip 3
section 1 item 4 --> Trip 4
section 1 item 5 --> Trip 5

now If a trip is deleted, the DiffableDataSource updates the change by animation but it doesn't reload the Headers of the sections. Which looks inconsistent. E.g. If the Trip 4 was deleted then Header still shows that there are 5 trips in the section. How can I have headers also reload with the DiffableDataSource?

for a temporary fix, I just call collectionView.reloadData() after a delay which shows the Diffing animation and then I hard reload the data which forces the header to be reloaded as well.

private func configureTripDataSource(){
    tripDataSource = UICollectionViewDiffableDataSource<MonthSection, Trip>(collectionView: tripsCollectionView, cellProvider: { (collectionView, indexPath, trip) -> UICollectionViewCell? in

        // Get a cell of the desired kind.
        guard let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: TripInfoCell.reuseIdentifier,
            for: indexPath) as? TripInfoCell else { fatalError("Cannot create new TripInfoCell") }

        // Populate the cell with our item description.
        cell.trip = trip

        // Return the cell.
        return cell

    })

    tripDataSource.supplementaryViewProvider = {
       [weak self] (collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> UICollectionReusableView? in

        guard let self = self else {return nil}

        if kind == TripsController.tripsMonthSectionHeaderElementKind{

            // Get a supplementary view of the desired kind.
            guard let header = collectionView.dequeueReusableSupplementaryView(
                ofKind: kind,
                withReuseIdentifier: TripSectionHeaderCell.reuseIdentifier,
                for: indexPath) as? TripSectionHeaderCell else { fatalError("Cannot create new header") }

            // setup header

            let currentSnapShot = self.tripDataSource.snapshot()
            let tripMonthSection = currentSnapShot.sectionIdentifiers[indexPath.section]

            header.titleLabel.text = tripMonthSection.title
            header.subtitleLabel.text = "\(tripMonthSection.trips.count) Trips"

            return header

        } else {
            return UICollectionReusableView()
        }

    }

    var snapshot = NSDiffableDataSourceSnapshot<MonthSection, Trip>()

    let allSections = self.tripsStore.monthSections
    snapshot.appendSections(allSections)
    for section in allSections{
        snapshot.appendItems(section.trips, toSection: section)
    }

    self.tripDataSource.apply(snapshot, animatingDifferences: true)
}
like image 531
FE_Tech Avatar asked Jan 30 '20 06:01

FE_Tech


2 Answers

To trigger an automatic reload of headers your Section object should be Hashable and should have all the necessary properties stored to create a unique hash for the Section.

That's why all Section objects and Item objects should be Hashable and they should return a unique hash to allow DiffableDataSource to manage their reload only if their values were changed.

For example:

struct MonthSection: Hashable {
   var title: String
   var itemsCount: Int
}

And then:

var section = MonthSection(title: "Title", itemsCount: 5)
.....
snapshot.appendSections([section])
snapshot.appendItems(items, toSection: section)

Any change of the title or items count for the section during the next update of the snapshot will trigger section header to reload and it will work like magic!

like image 199
IntMatrix Avatar answered Sep 25 '22 19:09

IntMatrix


in addition to changing the section identifier, it's HASH value, which is sometimes not desired, you can also set the

reloadSections of the snapshot NSDiffableDataSourceSnapshot

https://developer.apple.com/documentation/uikit/nsdiffabledatasourcesnapshot/3375784-reloadsections

this will trigger the section to update with footers and headers

func buildSnapshotAndApply(animated: Bool = true) {
    
    var newSnapshot = buildSnapshot()
    newSnapshot.reloadSections(snapshot().sectionIdentifiers)
    
    apply(newSnapshot, animatingDifferences: animated)
}
like image 23
Peter Lapisu Avatar answered Sep 24 '22 19:09

Peter Lapisu