Auto sizing collection view cell size ignores auto layout on first display

2.5k Views Asked by At

Situlation

I made a horizontal scrolling collection view with UICollectionViewFlowLayout.automaticSize. The collection view has height of 40 The cell has height of 40 which I set by autolayou on init.

Problem

The problem is that ONLY cells displayed on first displaying the collection view has size of 50x50 which overridden autolayout height of 40 I manually set by code.

I checked the size on willDisplayCell delegate method.

When I scroll the collection view to display other cells, each newly displayed cells have proper size with heigh of 40.

Although the cells with 50x50 size(as logged) are being displayed correctly(with height 40 it seems) on the screen.

From this, I get error messaged(logged) when I run the app on simulator and the app crashed on if I run on my real device(iPhone8 iOS 11).

Code

ViewController

import UIKit

class HorizontalTabNavigationViewController: UIViewController {

    // MARK: - Collection View

    private lazy var navigationCollectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        cv.translatesAutoresizingMaskIntoConstraints = false
        cv.backgroundColor = .white
        cv.dataSource = self
        cv.delegate = self
        return cv
    }()

    private typealias Cell = HorizontalTabNavigationCell
    private let cellId: String = "cell"



    // MARK: - Property

    var items: [HorizontalTabNavigationItem] = []



    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.translatesAutoresizingMaskIntoConstraints = false
        navigationCollectionView.register(Cell.self, forCellWithReuseIdentifier: cellId)
        navigationCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        setupSubviews()
    }


    private func setupSubviews() {

        view.addSubview( navigationCollectionView )

        [
        navigationCollectionView.topAnchor   .constraint(equalTo: view.topAnchor),
        navigationCollectionView.leftAnchor  .constraint(equalTo: view.leftAnchor),
        navigationCollectionView.rightAnchor .constraint(equalTo: view.rightAnchor),
        navigationCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ].forEach { $0.isActive = true }
    }


}


extension HorizontalTabNavigationViewController: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! Cell
        //cell.setProperties(item: items[indexPath.row])
        //print("Cell for item: \(items[indexPath.row].title)")

        return cell
    }

    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        print("Cell Size on willDispaly cell")
        print(cell.bounds.size)
        print("\n")
    }
}


extension HorizontalTabNavigationViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 5 // Defines horizontal spacing between cells
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return .zero
    }
}

Cell

class HorizontalTabNavigationCell: UICollectionViewCell {

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupSelfHeight()
        self.backgroundColor = .orange
    }

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

    private func setupSelfHeight() {
        self.heightAnchor.constraint(equalToConstant: 40).isActive = true
        self.widthAnchor.constraint(equalToConstant: 120).isActive = true
    }
}
1

There are 1 best solutions below

0
On

I solved the problem by setting custom size to flowlayout.

Inspired by Manav's anwer on this question below.

the behavior of the UICollectionViewFlowLayout is not defined, because the cell width is greater than collectionView width

private lazy var navigationCollectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal

        // [Here is the answer]
        // Set Height equal or less than the height of the collection view.
        // This is applied on first display of cells and 
        // will also not affect cells auto sizing.
        layout.estimatedItemSize = CGSize(width: 200, height: 40) // UICollectionViewFlowLayoutAutomaticSize

        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        cv.translatesAutoresizingMaskIntoConstraints = false
        cv.showsHorizontalScrollIndicator = false
        cv.backgroundColor = .white
        cv.dataSource = self
        cv.delegate = self
        return cv
    }()