iosswiftuicollectionviewuicollectionviewcompositionallayoutuicollectionviewdiffabledatasource

Swift: How to Optionally return a header for a collection view section?


I have a UICollectionViewDiffableDataSourcelike so:

var data:UICollectionViewDiffableDataSource<Section, Message>!

I define my section header's layout like so:

let header = NSCollectionLayoutBoundarySupplementaryItem(
    layoutSize: .init(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(10)),
    elementKind: UICollectionView.elementKindSectionHeader,
    alignment: .top
)
section.boundarySupplementaryItems = [header]

Lastly, to return my header, I have this function that returns the UICollectionReusableView like so:

func setupHeaderData() {
    data.supplementaryViewProvider = { collectionView, kind, indexPath in
        return DateStampBuilder(data: self.data, style: self.style).build(collectionView: collectionView, kind: kind, indexPath: indexPath)
    }
}

What's great: I can see my header's in my UICollectionView.

What I want: How can I optionally decide not to show a specific header for a specific section?

When I try to return nil in the following function:

data.supplementaryViewProvider = { collectionView, kind, indexPath in

I get the following error:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the view returned from -collectionView:viewForSupplementaryElementOfKind:atIndexPath (UICollectionElementKindSectionHeader,<NSIndexPath: 0xb461f3e1dd0c21dc> {length = 2, path = 0 - 0}) was not retrieved by calling -dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath: or is nil ((null))'

My only thoughts on how to "optionally" return a header for a section is to register another header view that has a height of zero and return this header when I don't want to return any header at all.

But to me, this seems like a bit of a messy approach, it would be much cleaner if I could just return nil when I do not what to show the header.

What am I doing wrong?


Thanks for your help!


Solution

  • This should be done when creating the layout for the collectionView. Here's an example on how to do it using UICollectionLayoutListConfiguration:

    let sectionProvider = { [weak self] (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            var section: NSCollectionLayoutSection
            
            var listConfiguration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
            
            if condition {
                listConfiguration.headerMode = .supplementary
            } else {
                listConfiguration.headerMode = .none
            }
            
            section = NSCollectionLayoutSection.list(using: listConfiguration, layoutEnvironment: layoutEnvironment)
            return section
        }
        
    let layout = UICollectionViewCompositionalLayout(sectionProvider: sectionProvider)
        
    collectionView.setCollectionViewLayout(layout, animated: true)
    

    Your condition can obviously be bound to your dataSource and section indexes so you do not run out of sync with dynamically created sections.