Hide empty section in Compositional Layout Collection View with DiffableDataSource not working

1.1k views Asked by At

I have three sections of data on screen: one for Five Star Items, one for Visits, and one for Other Fun Places items. They are all of the same type: funPlaces. For this example, I'm creating three arrays of funPlaces and showing them in three sections.

What I'd like to do is not generate a section on-screen at all if there is no data available for that section. So if there is no Visits data, I only want to see the sections for Five Star Items and Other Fun Places items. If there are no Five Star Items, I only want to see the sections for Visits and Other Fun Places items, etc.

However...

What's happening is that if there are no Visits items for example, rather than the Visits section disappearing as I would expect, instead the data from otherFunPlaces gets shown in the Visits section on-screen and the entire Other Fun Places section disappears. Yes, the Visits data is gone, but the section is still there and its data has been replaced with Other Fun Places data on-screen.

If I have an empty Five Star Items array, the Five Star Items section is still shown but the data has been replaced with Visits data on-screen. And the Other Fun Places data shifts up a section to the Visits Section--it's the Other Fun Places section that actually disappears on-screen.

The only case that works is if I have an empty Other Fun Items array--the section disappears as expected, and the data in the two sections above is also in its proper place on-screen.

What am I missing to make this work?

    func applySnapshot(animatingDifferences: Bool = true) {
        let funPlaces = fetchedResultsController.fetchedObjects ?? []

        if funPlaces.count > 6 {
            // we only generated enough sample data objects in viewDidLoad for this test

            // we're going to make sure there are no fiveStarItems in the array for this test
            
            // let fiveStarItems = Array<FunPlace>(funPlaces[0...1])
            let fiveStarItems: [FunPlace] = []  // hide fiveStarItems

            // we're going to keep data in visitsItems and otherItems
            let visitsItems = Array<FunPlace>(funPlaces[2...3]) 
            let otherFunPlacesItems = Array<FunPlace>(funPlaces[4...5])

        var snapshot = NSDiffableDataSourceSnapshot<Sections, FunPlace>()

        if !fiveStarItems.isEmpty {
            snapshot.appendSections([Sections.fiveStar])
            snapshot.appendItems(fiveStarItems, toSection: .fiveStar)
        }

        if !visitsItems.isEmpty {
            snapshot.appendSections([Sections.visits])
            snapshot.appendItems(visitsItems, toSection: .visits)
        }

        if !otherFunPlacesItems.isEmpty {
            snapshot.appendSections([Sections.otherFunPlaces])
            snapshot.appendItems(otherFunPlacesItems, toSection: .otherFunPlaces)
        }

        dataSource?.apply(snapshot, animatingDifferences: true)
        }
    }

Thanks.

2

There are 2 answers

2
Kent On

I figured it out.

The problem was indexing the sections to lay out. I had to keep track of available sections separately and only lay out the ones available.

0
christostsang On

When creating the layout make sure you do it based on the section identifier, not the section number. This way, any section with no data will not be displayed:

let layout = UICollectionViewCompositionalLayout(sectionProvider: { sectionNumber, env in
    let sectionIdentifier = self.dataSource.snapshot().sectionIdentifiers[sectionNumber]

    if sectionIdentifier == .fiveStar {
         return FiveStarSectionLayout....
    } else if (sectionIdentifier == .visits) {
         return VisitsSectionLayout....
    } else if sectionIdentifier == .otherFunPlaces) {
          return OtherFunPlacesSectionLayout...
    } else {
          return nil
    }

})
        
self.collectionView.setCollectionViewLayout(layout, animated: true)