Search code examples
swiftuicollectionviewuicollectionviewcelluicollectionviewcompositionallayout

UICollectionViewCompositionalLayout Collectionview cell width not resizing properly after reloading data


I Am using UICollectionViewCompositionalLayout to create dynamic width, fixed height, tag cell type of layout in some sections of my collectionview. When i add a new string to an array in my database, reload array into my datasource and do a collectionview reloaddata, the cells resize in an unpredictable way. sometimes they resize correctly to fit the width of the string and sometimes they seem to use the estimated width of the cell and truncate the string. Any suggestions on how to fix this? I have posted a gif below.

enter image description here

Here is my makelayout function (simplified and removed unnecessary lines of code):

override func makeLayout() -> UICollectionViewLayout {
        let layout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex, environment) -> NSCollectionLayoutSection? in


        let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(40), heightDimension: .absolute(40))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        let inset: CGFloat = 10
        item.contentInsets = NSDirectionalEdgeInsets(top: inset, leading: inset, bottom: inset, trailing: inset)
            
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(40))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        group.interItemSpacing = NSCollectionLayoutSpacing.fixed(CGFloat(10))
        let section = NSCollectionLayoutSection(group: group)
        section.interGroupSpacing = CGFloat(10.0)
        section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20)
        section.supplementariesFollowContentInsets = false

        return section
          
    })
        
        
    let config = UICollectionViewCompositionalLayoutConfiguration()
    config.scrollDirection = .vertical
    config.interSectionSpacing = 10
    layout.configuration = config
      
    return layout
}

here is my custom collectionviewcell

class CollectionViewCell: UICollectionViewCell {
    
    lazy var textLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont(name: "Stolzl-Regular", size: 12)
        label.textAlignment = NSTextAlignment.left
        label.backgroundColor = .clear //.red
        label.textColor = .white
        label.text = ""
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        label.lineBreakMode = NSLineBreakMode.byWordWrapping
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)

        setupLabel()

    }
    
    func setupLabel(){
        addSubview(textLabel)
        addConstraintsWithFormat("H:|-10-[v0]-10-|", views: textLabel)
        addConstraintsWithFormat("V:|-10-[v0]-10-|", views: textLabel)
    }

    override func prepareForReuse() {
        super.prepareForReuse()

    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Solution

  • We obviously don't know how apple decided to implement .estimated(n) layout mechanics. If we can see their code, we can probably pinpoint exactly what's going on here. But I have seen similar UI layout issues in my own applications in the past. I believe the problem lies within the interplay between the following lines of code:

    let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(40),  heightDimension: .absolute(40)).
    label.numberOfLines = 0
    label.lineBreakMode = NSLineBreakMode.byWordWrapping
    

    You are essentially under-constraining your cell. a) You are allowing infinite number of lines on your label. b) You are also allowing the lines to be broken by words. c) Moreover you are telling the layout engine that you don't know the size of the cell (or label - since this is the only thing in the cell). After the reload of the view, the truncated labels are also satisfying those constraints you placed on them, even though, it was probably not your intention.

    When the constraints are ambiguous there are multiple ways to satisfy the constraint. It looks like during the first layout pass, the layout engine picks one way to satisfy these constraints and when you reload the collection view, the layout engine picks another way. Ideally, the layout engine is consistent and always uses the same method to perform the layout. This might be where the issue is. I think if you switch the numberOfLines property to 1 and NSLineBreakMode to byTruncatingTail, my guess is that you are going to have more consistent layout experience.

    I would also suggest to adjust your estimated size as it is supposed to be close to an average size of your cells. Looking at your possible values that can be in there from the table view in your gif - I can tell that 40 pts is much too small. Maybe 120 pts is more a realistic estimate.