DiffableDataSource: Snapshot Doesn't reload Headers & footers
Asked Answered
H

2

12

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)
}
Homozygote answered 30/1, 2020 at 6:40 Comment(4)
Please add the code related to the headers and footers.Cleanup
@vadian, Okay, I have added the code.Homozygote
Sorry, I overlooked that you are using supplementaryViewProvider which I'm not familiar with.Cleanup
I have the exact same issue. I've tried nilling out the header and re-setting it but no luck. It only updates when it scrolls off the screen and comes back.Deplore
I
17

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!

Intermission answered 11/3, 2020 at 11:35 Comment(1)
thank you! I have Section washable object. But I use inherit subclass of this Section class and use variable there. Now I move variable to main class and everything work like charm!Abrams
M
3

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)
}
Margarettamargarette answered 9/12, 2020 at 16:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.