3

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.

nayem
  • 7,285
  • 1
  • 33
  • 51

2 Answers2

5

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

good4pc
  • 711
  • 4
  • 17
  • Great. Actually that's it. But I think it needs a detailed answer for future readers coming with same problem. For this I'm adding another answer. – nayem Apr 05 '20 at 19:29
  • I was going crazy trying to figure this out! Ha! yes this was it I forgot to set the class in the xib! Thank you – devjme Jun 23 '22 at 15:07
  • Very helpful!! Xcode doesn't add the subclass name in XIB created with "UICollectionReusableView". – Akash Bhardwaj Jul 08 '22 at 11:23
2

Well, after some more investigation and the input from @good4pc in the accepted answer (actually I found out that by myself before looking at the answer) it seems that the issue is actually happening for some unwanted issue with Xcode.

When we create any view (preferably, UITableViewCell or UICollectionViewCell) with .xib, the class identity is provided automatically for that .xib in the identity inspector. But this was not the case for UICollectionReusableView. See the attached screenshot below for easy understanding.

This is a UICollectionViewCell subclassed with .xib:

UICollectionViewCell custom class

This is a UICollectionReusableView subclassed with .xib:

UICollectionReusableView custom class


So the key is to provide the class identity of the .xib file which is done from the attributes inspector.

nayem
  • 7,285
  • 1
  • 33
  • 51