Swift, iOS15, UIKit, CollectionView header issue

I am testing iOS15 and some new functionalities of UIKit. I've encountered some issues, not sure how to solve them. I did not change that code. This is just a piece of code that worked perfectly with the iOS 14, now after updating my target, it throws an error.

Xcode crashes the moment when my custom header for the UICollectionView of type UICollectionElementKindSectionHeader is being returned for the dataSource. Here is my code:

private func configureDataSource() {
      dataSource = UICollectionViewDiffableDataSource<Section, Follower>(collectionView: collectionView, cellProvider: { (collectionView, indexPath, followers) -> UICollectionViewCell? in
         let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FollowerCell.reuseId, for: indexPath) as! FollowerCell
         cell.set(on: followers)
         return cell
      })
      
      dataSource.supplementaryViewProvider = { (collectionView, kind, indexPath) in
         let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader,
                                                                      withReuseIdentifier: FollowersCollectionHeaderView.reuseId,
                                                                      for: indexPath) as! FollowersCollectionHeaderView
         
         header.set(with: self.user)
         return header
      }
   }

The log says:

the view returned from -collectionView:viewForSupplementaryElementOfKind:atIndexPath: does not match the element kind it is being used for. When asked for a view of element kind 'FollowersCollectionHeaderView' the data source dequeued a view registered for the element kind 'UICollectionElementKindSectionHeader'.

I did cast UICollectionElementKindSectionHeader to FollowersCollectionHeaderView, therefore I am not sure what is the issue here.

I've watched WWDC21 what's new in UIKit but haven't seen any mentioning of any change for that particular code.

Any suggestions, what to fix in that code?

Answered by Frameworks Engineer in 691063022

In order to understand the problem, we need to review how UICollectionView supplementary and decoration views work.

Supplementary (and Decoration) Views

Immediately before the crash, UICollectionView asked your data source for a supplementary view, either by calling the method collectionView(_:viewForSupplementaryElementOfKind:at:) (documentation) if you have a custom data source implementation, or by calling your supplementaryViewProvider (documentation) if you are using a diffable data source.

When the collection view asks you for a supplementary view, it passes a String that represents the element kind of the supplementary that the collection view needs.

The element kind is used to differentiate multiple types of supplementary and/or decoration views — for example, you might have two supplementary views for each section of a list: a section header and a section footer.

Defining Element Kinds

Your collection view layout is what defines the element kinds you are using for supplementary and decoration views.

Collection view lists built with UICollectionLayoutListConfiguration and flow layouts built with UICollectionViewFlowLayout utilize special string constants for their supplementary view element kinds:

If you're building a custom UICollectionViewCompositionalLayout, you choose the element kind strings for your supplementary and decoration views when defining your layout using NSCollectionLayoutSupplementaryItem, NSCollectionLayoutBoundarySupplementaryItem, and NSCollectionLayoutDecorationItem.

Using Element Kinds

When you register a class or nib for a supplementary, you specify the element kind that you are creating the registration for.

  • Each SupplementaryRegistration is created with the element kind.
  • Or, if you're still using the methods to register a class or nib manually, you specify both the element kind and reuse identifier that you are registering for.

You need at least one supplementary registration for each element kind that your layout uses.

Then, when the collection view needs a supplementary view, it asks its data source to provide the view for the specific element kind it needs at that particular moment. If you have multiple types of supplementary views, each element kind is requested separately, and depending on your layout, you might even have multiple supplementaries (with different element kinds) for the same index path.

Therefore, it is your responsibility to look at the element kind of the supplementary view that UICollectionView is asking for, and make sure that you dequeue & return a supplementary view for that element kind. (You can have multiple SupplementaryRegistration or registered reuse identifiers for each element kind, if desired, and it's up to you to pick which one to use. But you must always dequeue a supplementary view for the element kind that the collection view is requesting.)

Fixing the Crash

With that understanding, let's revisit the exception message for this crash, in particular the second sentence:

When asked for a view of element kind 'FollowersCollectionHeaderView' the data source dequeued a view registered for the element kind 'UICollectionElementKindSectionHeader'.

What this is telling you is that UICollectionView asked your data source for a supplementary view for element kind 'FollowersCollectionHeaderView', but you dequeued & returned a supplementary view for a different element kind 'UICollectionElementKindSectionHeader' (which is the raw string value of the special elementKindSectionHeader constant).

To fix this, you'll need to look at the implementation of your collectionView(_:viewForSupplementaryElementOfKind:at:) method or supplementaryViewProvider on your data source. Make sure that you always use the element kind passed in when you go to dequeue a supplementary view in your implementation. You are free to choose any SupplementaryRegistration or reuse identifier that you previously registered for that element kind, but you cannot dequeue & return a supplementary view for a different element kind than the one the collection view is requesting.

I'm having this exact same problem. Here's my error message: *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the view returned from -collectionView:viewForSupplementaryElementOfKind:atIndexPath: does not match the element kind it is being used for. When asked for a view of element kind 'UICollectionElementKindSectionHeader' the data source dequeued a view registered for the element kind 'FeedCollectionViewHeader'.'

Anyone find a solution to this?

Same problem here:

The view returned from -collectionView:viewForSupplementaryElementOfKind:atIndexPath: does not match the element kind it is being used for. When asked for a view of element kind 'UICollectionElementKindSectionHeader' the data source dequeued a view registered for the element kind 'UICollectionElementKindSectionFooter

I had the same problem and it was because iOS 15 seems to not support registering the same class for multiple element kinds. I made subclasses of the original class and registered each one for each kind and the crash fixed.

Similar issue with me

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the view returned from -collectionView:viewForSupplementaryElementOfKind:atIndexPath: was not retrieved by calling -dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath: for element kind 'UICollectionElementKindSectionFooter' at index path <NSIndexPath: 0xb271fbe1238032dc> {length = 2, path = 1 - 0}; supplementary view: <UICollectionReusableView: 0x104a6cfb0; frame = (0 0; 0 0); layer = <CALayer: 0x2813ecc80>>'

Hi

If anyone still having same problem, try this:

In my case I faced the problem when I was using list layout

I changed my code from this:

private func createListLayout() -> UICollectionViewLayout {

    var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)

    config.backgroundColor = UIColor.white

    config.headerMode = .supplementary

    let layout = UICollectionViewCompositionalLayout.list(using: config)


    return layout

}

To this code:

 private func createListLayout() -> UICollectionViewLayout {

    let layout = UICollectionViewCompositionalLayout() { sectionIndex, layoutEnvironment in

        var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)

        config.backgroundColor = UIColor.white

        config.headerMode = .supplementary

        let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment)

        let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(144))

        let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(

            layoutSize: headerSize,

            elementKind: "myElementKind", alignment: .top)

        section.boundarySupplementaryItems = [sectionHeader]


        return section
    }


    return layout
}

And this is my provider if you need:

dataSource.supplementaryViewProvider = { (collectionView, kind, indexPath) in

    let header = collectionView.dequeueReusableSupplementaryView(
                               ofKind: "myElementKind",
                               withReuseIdentifier: "myIdentifier",
                               for: indexPath) as! MyCustomHeader

    // code


    return header
}

This worked for me

I hope this works for you as well.

Best regards.

I had the same issue, mostly using IGListKit delegate method:

func viewForSupplementaryElement(ofKind elementKind: String, at index: Int) -> UICollectionReusableView

Instead of using

context.dequeueReusableCell

I changed that call for

context.dequeueReusableSupplementaryView

So the internal IGListKit implementation can use

UICollectionView.dequeueReusableSupplementaryViewOfKind

instead of

UICollectionView.dequeueReusableCellWithReuseIdentifier

That worked for me.

Sorry I forgot to paste the solution I came up with. It should all work now.

enum Section { case main }


var dataSource: UICollectionViewDiffableDataSource<Section, Follower.ID>!

// MARK: - Collection View configurations
    fileprivate lazy var collectionView: UICollectionView = {
        let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: UIHelper.createCompositionalLayout())
        collectionView.delegate = self
        collectionView.backgroundColor = .systemBackground
        collectionView.register(FollowersCollectionHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: FollowersCollectionHeaderView.reuseId)
        view.addSubview(collectionView)
        return collectionView
    }()
    
    fileprivate lazy var snapshot: NSDiffableDataSourceSnapshot<Section, Follower.ID> = {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Follower.ID>()
        snapshot.appendSections([.main])
        let itemIdentifiers = followers.map { $0.id }
        snapshot.appendItems(itemIdentifiers, toSection: .main)
        dataSource.apply(snapshot, animatingDifferences: true)
        return snapshot
    }()
    
    fileprivate func updateData(with followers: [Follower]) {
        snapshot = NSDiffableDataSourceSnapshot<Section, Follower.ID>()
        snapshot.appendSections([.main])
        let itemIdentifiers = followers.map { $0.id }
        snapshot.appendItems(itemIdentifiers, toSection: .main)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
    
    fileprivate func configureDataSource() {
        let cellRegistration = UICollectionView.CellRegistration<FollowerCell, Follower.ID> { [weak self]
            cell, indexPath, followerID in
            guard let self = self else { return }
            
            let followerArray = self.followers.filter { $0.id == followerID }
            
            if let follower = followerArray.first {
                cell.set(on: follower)
            }
        }
        
        dataSource = UICollectionViewDiffableDataSource<Section, Follower.ID>(collectionView: collectionView) {
            collectionView, indexPath, itemIdentifier in
            
            return collectionView.dequeueConfiguredReusableCell(using: cellRegistration,
                                                                for: indexPath,
                                                                item: itemIdentifier)
        }
        
        let headerRegistration = createSectionHeaderRegistration()
        dataSource.supplementaryViewProvider = { collectionView, elementKind, indexPath in
            return collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration, for: indexPath)
        }
    }
    
    fileprivate func createSectionHeaderRegistration() -> UICollectionView.SupplementaryRegistration<FollowersCollectionHeaderView> {
        return UICollectionView.SupplementaryRegistration<FollowersCollectionHeaderView>(
            elementKind: FollowersCollectionHeaderView.reuseId) { [weak self] supplementaryView, elementKind, indexPath in
            guard let self = self else { return }
            supplementaryView.set(with: self.user)
        }
    }

This here strangely solved it for me:

lazy var collectionView: UICollectionView = {
    let collectionView = UICollectionView(/**/)
    // ...
     collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: HeaderView.reuseId)
     collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: HeaderView.reuseId)
  // ....
  return collectionView 
}()

And where I reuse the theader:

func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
    let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: HeaderView.reuseId, for: indexPath) as! HeaderView
// ... update header with data
return header 
}

In my collectionView I am only using headers. No footers at all.

I solve this issue. You can see it in stackoverflow

In order to understand the problem, we need to review how UICollectionView supplementary and decoration views work.

Supplementary (and Decoration) Views

Immediately before the crash, UICollectionView asked your data source for a supplementary view, either by calling the method collectionView(_:viewForSupplementaryElementOfKind:at:) (documentation) if you have a custom data source implementation, or by calling your supplementaryViewProvider (documentation) if you are using a diffable data source.

When the collection view asks you for a supplementary view, it passes a String that represents the element kind of the supplementary that the collection view needs.

The element kind is used to differentiate multiple types of supplementary and/or decoration views — for example, you might have two supplementary views for each section of a list: a section header and a section footer.

Defining Element Kinds

Your collection view layout is what defines the element kinds you are using for supplementary and decoration views.

Collection view lists built with UICollectionLayoutListConfiguration and flow layouts built with UICollectionViewFlowLayout utilize special string constants for their supplementary view element kinds:

If you're building a custom UICollectionViewCompositionalLayout, you choose the element kind strings for your supplementary and decoration views when defining your layout using NSCollectionLayoutSupplementaryItem, NSCollectionLayoutBoundarySupplementaryItem, and NSCollectionLayoutDecorationItem.

Using Element Kinds

When you register a class or nib for a supplementary, you specify the element kind that you are creating the registration for.

  • Each SupplementaryRegistration is created with the element kind.
  • Or, if you're still using the methods to register a class or nib manually, you specify both the element kind and reuse identifier that you are registering for.

You need at least one supplementary registration for each element kind that your layout uses.

Then, when the collection view needs a supplementary view, it asks its data source to provide the view for the specific element kind it needs at that particular moment. If you have multiple types of supplementary views, each element kind is requested separately, and depending on your layout, you might even have multiple supplementaries (with different element kinds) for the same index path.

Therefore, it is your responsibility to look at the element kind of the supplementary view that UICollectionView is asking for, and make sure that you dequeue & return a supplementary view for that element kind. (You can have multiple SupplementaryRegistration or registered reuse identifiers for each element kind, if desired, and it's up to you to pick which one to use. But you must always dequeue a supplementary view for the element kind that the collection view is requesting.)

Fixing the Crash

With that understanding, let's revisit the exception message for this crash, in particular the second sentence:

When asked for a view of element kind 'FollowersCollectionHeaderView' the data source dequeued a view registered for the element kind 'UICollectionElementKindSectionHeader'.

What this is telling you is that UICollectionView asked your data source for a supplementary view for element kind 'FollowersCollectionHeaderView', but you dequeued & returned a supplementary view for a different element kind 'UICollectionElementKindSectionHeader' (which is the raw string value of the special elementKindSectionHeader constant).

To fix this, you'll need to look at the implementation of your collectionView(_:viewForSupplementaryElementOfKind:at:) method or supplementaryViewProvider on your data source. Make sure that you always use the element kind passed in when you go to dequeue a supplementary view in your implementation. You are free to choose any SupplementaryRegistration or reuse identifier that you previously registered for that element kind, but you cannot dequeue & return a supplementary view for a different element kind than the one the collection view is requesting.

Swift, iOS15, UIKit, CollectionView header issue
 
 
Q