Search code examples
iosswiftuicollectionviewuicollectionreusableview

How to use custom UICollectionReusableView as section header of collection view?


I've been driven insane for hours as I can't get around with the issue.

I have a collection view which can have different section with different no. of items in each. For each section I need to use a section header of different type. So for this, I'm going to use UICollectionReusableView. But I can't seem to succeed in using a custom subclass of UICollectionReusableView by means of UINib registration.

A crash happens when I downcast the reusable view to my subclass. Like:

let friendHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, 
                                    withReuseIdentifier: "FriendHeaderView", 
                                    for: indexPath) as! FriendHeaderView

Below is the code snippet:

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    private let viewModel = ProfileViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.dataSource = self
        collectionView.delegate = self
        // more code
        collectionView.register(UINib(nibName: "FriendHeaderView", bundle: nil), 
                                forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, 
                                withReuseIdentifier: "FriendHeaderView")
    }
}

Now here is the data source implementation:

extension ViewController: UICollectionViewDataSource {

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        // valid implementation
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        // valid implementation
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        // valid implementation

    }

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

        switch kind {
        case UICollectionView.elementKindSectionHeader:
            let friendHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "FriendHeaderView", for: indexPath) as! FriendHeaderView

            // *** Crash happens here *** //

            return friendHeader
        default:
            assert(false, "Invalid element type")
        }

    }

}

And I don't know why the collectionView(_:layout:referenceSizeForHeaderInSection:) needs to be also implemented. So here it is:

extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        let size = CGSize(width: collectionView.bounds.width, height: 100)
        return size
    }

}

Okay, now come to the point: The above mentioned crash doesn't happen at all if I don't downcast with as! operator. Well, if I use section header from the storyboard instead of UINib registration, there is no crash.

If I'm going to need multiple type header, then I can't also use storyboard approach or without down-casting approach as I need to feed data to those headers.


What can I do to have multiple type headers with view built from interface builder?


I've made a demo project with what I've said above. If anyone is interested please check out that.


Solution

  • Once you assign proper class and identifier in your Xib file, then it will work without crashes.