UICollectionView Auto cell height problem

Hi, I'm trying to create a UICollectionView where the cell high is automatic. Cells contains a UILabel with all anchors to the contentView of the cell. It seems to work but I have a strange behavior with longer text, on reload data and on device rotation: Cells do not display the whole text or they change row, both randomly.

To create my collection view I first create the collection view with a custom flow layout setting the automatic size on viewWillAppear:

let collectionViewFlowLayout = CustomFlowLayout()
collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowLayout)

and I have also overridden:

override func willAnimateRotation(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
        collectionView.setNeedsLayout()
        self.collectionView.layoutIfNeeded()
        self.collectionView.collectionViewLayout.invalidateLayout()
        //self.collectionView.reloadData()
    }

Then, I created the custom layout as follow:

import UIKit

final class CustomFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
        layoutAttributesObjects?.forEach({ layoutAttributes in
            if layoutAttributes.representedElementCategory == .cell {
                if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
                    layoutAttributes.frame = newFrame
                }
            }
        })
        return layoutAttributesObjects
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let collectionView = collectionView else {
            fatalError()
        }
        guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
            return nil
        }

        layoutAttributes.frame.origin.x = sectionInset.left
        
        if(indexPath.section == 0){
            layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right
        } else if (indexPath.section == collectionView.numberOfSections - 1){
            let width = ScreenUtility.getCollectionCellWidthForElement(in: collectionView, sectionLeft: sectionInset.left, sectionRight: sectionInset.right, minimumInteritemSpacing: minimumInteritemSpacing, multiplier: 3)
            layoutAttributes.frame.origin.x = ScreenUtility.getCollectionCellOriginForElement(in: collectionView, at: indexPath, forElementHavingWidth: width, sectionLeft: sectionInset.left, sectionRight: sectionInset.right, minimumInteritemSpacing: minimumInteritemSpacing, multiplier: 3)
            layoutAttributes.frame.size.width = width
        } else if (indexPath.section == collectionView.numberOfSections - 3) || (indexPath.section == collectionView.numberOfSections - 4){
            let width = ScreenUtility.getCollectionCellWidthForElement(in: collectionView, sectionLeft: sectionInset.left, sectionRight: sectionInset.right, minimumInteritemSpacing: minimumInteritemSpacing)
            layoutAttributes.frame.origin.x = ScreenUtility.getCollectionCellOriginForElement(in: collectionView, at: indexPath, forElementHavingWidth: width, sectionLeft: sectionInset.left, sectionRight: sectionInset.right, minimumInteritemSpacing: minimumInteritemSpacing)
            layoutAttributes.frame.size.width = width
        } else {
            let width = ScreenUtility.getCollectionCellSizeForElementFullRow(in: collectionView, sectionLeft: sectionInset.left, sectionRight: sectionInset.right)
            layoutAttributes.frame.origin.x = ScreenUtility.getCollectionCellOriginForElementFullRow(in: collectionView, sectionLeft: sectionInset.left, sectionRight: sectionInset.right)
            layoutAttributes.frame.size.width = width
        }
         
        return layoutAttributes
    }
}

And finally on collection view cells:

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        setNeedsLayout()
        layoutIfNeeded()
        let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
        layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
        return layoutAttributes
    }

override func prepareForReuse() {
        self.nameLabel.text = ""
        self.idLabel.text = ""
        self.contentView.setNeedsLayout()
        self.contentView.layoutIfNeeded()
    }

Let me show you an example on the iPad that is the worst. First Time I open the collection view I have cells on wrong rows and not sized properly

Then I rotate the device portrait and the cells are fine

On landscape again it changes behavior:

This is just an example, things happens apparently randomly, and also sometimes cells disappear (I think the height is set to 0).

I really do not understand why, cells width seems to be computed correctly, and cell label is set via setter:

open var step: String = "" {
        didSet {
            nameLabel.text = step
            nameLabel.sizeToFit()
            self.contentView.setNeedsLayout()
            self.contentView.layoutIfNeeded()
        }
    }

We would need a bug report with a focused sample to really debug what is happening but you may also want to consider using a compositional layout or UIHostingConfiguration with SwiftUI to achieve this with less hacks code for invalidation.

Hi @matmacsystem , well it really difficult to understand what's happening, but first at all you should clear what is the layout do you want to obtain. Then, you should, if possible, check the layout of the cell, if all constraints are validated during the execution of the app (check on debug console if some constraints are invalidate). At this point, probably problems could be on custom frame calculation (double check ScreenUtility.getCollectionCellWidthForElement()) . And I will try to reduce calling on setNeedsLayout and layoutIfNeeded.

Anyway, if you have a minimal sample project to share, we probably could help.

Bye Rob

UICollectionView Auto cell height problem
 
 
Q