7

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!

Rog
  • 18,602
  • 6
  • 76
  • 97
Josh Arnold
  • 249
  • 4
  • 8

3 Answers3

14

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.

schnabler
  • 687
  • 7
  • 23
3

I was looking at this recently and the solution I came up with was to optionally set the compositional layout boundarySupplementaryItems based on the same condition you use to return nil on the supplementaryViewProvider closure.

Some pseudo-code:

Given some condition

let myCondition = ...

My supplementary view provider:

dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in 
    // ...
    guard myCondition else { return nil }
}

And in your compositional layout provider function/closure you can:

let section = NSCollectionLayoutSection(...)
if myCondition {
    let header = NSCollectionLayoutBoundarySupplementaryItem(...)
    section.boundarySupplementaryItems = [header]
}

This would allow you to return nil from the supplementaryViewProvider closure because the collection view will never ask for a supplementary view when that condition is true (if it did, you would still see the crash).

Rog
  • 18,602
  • 6
  • 76
  • 97
1

Kind of an ugly solution but I create a simple empty view:

class EmptyHeader : UICollectionReusableView {    
    static var reuseIdentifier:String = "spacer"
}

Then I just return this view when I don't want a header. (Make sure to register the view with the collectionview first).

Josh Arnold
  • 249
  • 4
  • 8