iosuicollectionviewios-autolayoutxcode10ios12

In iOS 12, when does the UICollectionView layout cells, use autolayout in nib


Same code like this

collectionLayout.estimatedItemSize = CGSize(width: 50, height: 25)
collectionLayout.itemSize = UICollectionViewFlowLayoutAutomaticSize
collectionLayout.minimumInteritemSpacing = 10 

for _ in 0 ..< 1000 {
    let length = Int(arc4random() % 8)
    let string = randomKeyByBitLength(length)
    array.append(string!)
}
collectionView.reloadData()

cell constraints:

enter image description here

when I run it on iOS 12, it's different. left simulator is iOS 11, and right is iOS 12:

enter image description here

But, when I scroll it, cells's frames will be normal.


Sample project to reproduce the issue: https://github.com/Coeur/StackOverflow51375566


Solution

  • For all solutions, note that there is no need to explicitly call reloadData in viewDidLoad: it will happen automatically.

    Solution 1

    Inspired by Samantha idea: invalidateLayout asynchronously in viewDidLoad.

    override func viewDidLoad() {
        super.viewDidLoad()
    
        //[...]
    
        for _ in 0 ..< 1000 {
            array.append(randomKeyByBitLength(Int(arc4random_uniform(8)))!)
        }
    
        DispatchQueue.main.async {
            self.collectionView.collectionViewLayout.invalidateLayout()
        }
    }
    

    Solution 2

    (imperfect, see DHennessy13 improvement on it)

    Based on Peter Lapisu answer. invalidateLayout in viewWillLayoutSubviews.

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        collectionView.collectionViewLayout.invalidateLayout()
    }
    

    As noted by DHennessy13, this current solution with viewWillLayoutSubviews is imperfect as it will invalidateLayout when rotating the screen.

    You may follow DHennessy13 improvement regarding this solution.

    Solution 3

    Based on a combination of Tyler Sheaffer answer, Shawn Aukstak port to Swift and Samantha idea. Subclass your CollectionView to perform invalidateLayout on layoutSubviews.

    class AutoLayoutCollectionView: UICollectionView {
    
        private var shouldInvalidateLayout = false
    
        override func layoutSubviews() {
            super.layoutSubviews()
            if shouldInvalidateLayout {
                collectionViewLayout.invalidateLayout()
                shouldInvalidateLayout = false
            }
        }
    
        override func reloadData() {
            shouldInvalidateLayout = true
            super.reloadData()
        }
    }
    

    This solution is elegant as it doesn't require to change your ViewController code. I've implemented it on branch AutoLayoutCollectionView of this sample project https://github.com/Coeur/StackOverflow51375566/tree/AutoLayoutCollectionView.

    Solution 4

    Rewrite UICollectionViewCell default constraints. See Larry answer.

    Solution 5

    Implement collectionView(_:layout:sizeForItemAt:) and return cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize). See matt answer.