Home > Software design >  UICollectionView crashes when item size is automatic and header is shown
UICollectionView crashes when item size is automatic and header is shown

Time:12-29

I encounter a weird error that when I want to set the estimatedItemSize property of the UICollectionView to UICollectionViewFlowLayout.automaticSize and show a section header at the same time.

With the below code, the app can show the UICollectionView properly. However, once the user scrolls on the UICollectionView, the app crashes with a recursive call to the function updateVisibleCellsNow.

I find a workaround solution from another StackOverflow question by setting the estimatedItemSize from automaticSize to none. However, I want to keep the auto-layout features set at the UICollectionViewCell instead of calculating the cell height myself. Is there any better solution? Thank you.

enter image description here

Here is my code about the ViewController

import UIKit
class DemoCollectionViewController: UIViewController {
    
    lazy private var collectionView: UICollectionView = { [weak self] in
        guard let strongSelf = self else { return UICollectionView() }
        
        // Setup of `UICollectionViewFlowLayout`
        let flowLayout = UICollectionViewFlowLayout()
        

        // ********* This is the line that causes crash *********
        flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
        // ******************************************************

        flowLayout.headerReferenceSize = CGSize(width: getScreenWidth(), height: 44.0)
        flowLayout.sectionHeadersPinToVisibleBounds = true
        
        // Setup of `UICollectionView`
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout).disableFrameToAutoLayout()
        collectionView.dataSource = strongSelf
        collectionView.register(ProfileThumbnailCollectionViewCell.self, forCellWithReuseIdentifier: "cellId")
        collectionView.register(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "headerId")
        
        return collectionView
    }()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Setup of the UICollectionView
        view.addSubview(collectionView)
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            collectionView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            collectionView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor),
        ])
    }
}

extension DemoCollectionViewController: UICollectionViewDataSource {
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 10
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 20
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! ProfileThumbnailCollectionViewCell
        cell.contentView.backgroundColor = .yellow
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

        switch kind {
        case UICollectionView.elementKindSectionHeader:
            let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "headerId", for: indexPath)
            supplementaryView.backgroundColor = .red
            return supplementaryView
        default:
            return UICollectionReusableView()
        }
    }
}

Here is my code about the ProfileThumbnailCollectionViewCell:

class ProfileThumbnailCollectionViewCell: UICollectionViewCell {
    
    private let profileThumbnailImageView: UIImageView = {
        let profileThumbnailImageView = UIImageView()
        profileThumbnailImageView.translatesAutoresizingMaskIntoConstraints = false
        profileThumbnailImageView.backgroundColor = .red
        profileThumbnailImageView.layer.cornerRadius = 60
        profileThumbnailImageView.layer.masksToBounds = true
        return profileThumbnailImageView
    }()
    
    private let editPenButton: UIButton = {
        let editPenButton = UIButton()
        editPenButton.translatesAutoresizingMaskIntoConstraints = false
        editPenButton.backgroundColor = .mainGreen
        editPenButton.layer.cornerRadius = 16
        editPenButton.layer.masksToBounds = true
        return editPenButton
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        // Content View
        contentView.backgroundColor = .yellow
        NSLayoutConstraint.activate([
            contentView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width)
        ])
        
        // Profile Thumbnail ImageView
        contentView.addSubview(profileThumbnailImageView)
        NSLayoutConstraint.activate([
            profileThumbnailImageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            profileThumbnailImageView.widthAnchor.constraint(equalToConstant: 120),
            profileThumbnailImageView.heightAnchor.constraint(equalToConstant: 120),
            profileThumbnailImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 25),
            profileThumbnailImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -25)
        ])
        
        // Edit thumbnail pen ImageView
        contentView.addSubview(editPenButton)
        NSLayoutConstraint.activate([
            editPenButton.widthAnchor.constraint(equalToConstant: 32),
            editPenButton.heightAnchor.constraint(equalToConstant: 32),
            editPenButton.rightAnchor.constraint(equalTo: contentView.rightAnchor),
            editPenButton.rightAnchor.constraint(equalTo: contentView.rightAnchor)
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("This class should be inited from code instead of a nib file; init(coder:) has not been implemented")
    }
}

CodePudding user response:

call registerNib in ViewDidLoad

func registerNib(){
 let nib = UINib(nibName: "ProfileThumbnailCollectionViewCell", bundle: nil)
 self.collectionView.register(nib, forCellWithReuseIdentifier: "ProfileThumbnailCollectionViewCell")
}

CodePudding user response:

After investigating for a few hours, I accidentally found a workaround solution but surely this should not be considered as the final solution.

class ProfileThumbnailCollectionViewCell: UICollectionViewCell {
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        // Content View
        contentView.backgroundColor = .yellow

        // ******** This is the weird point ********
        let targetCellWidth = UIScreen.main.bounds.width - 1


        NSLayoutConstraint.activate([
            contentView.widthAnchor.constraint(equalToConstant: targetCellWidth)
        ])
    }
}
    

The UICollectionView does not throw any error and displays both the header view and the UICollectionView properly.

If you can find any idea about this situation, please leave your comment here. Your information is highly appreciated! Thank you!

  • Related