Swift: How to get correct UICollectionView height when using snapkit?

Viewed 3066

I'm likely going about this in the completely wrong way since what I try to do should be simple... I have a view component which contains a few subviews, one of them being a UICollectionView. I layout the subviews using SnapKit. The UICollectionView is a flowlayout of buttons, spanning over 0-n rows, number of buttons is known from the beginning.

When I configure the constraints via SnapKit the height of the Collection is 0, since no items are present (cellForItemAt still not called).

How would I go about to get the height of the collection to be dynamically set in SnapKit? Or is there a better way to get the very basic flowlayout I'm looking for (with dynamic heigth-adjustement)?

(Note: The code is a bit simplified to only show relevant parts)

class CategoryList: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate {

    var categories : [String]?
    var buttons = [BorderedButton]()

    private(set) lazy var collectionView: UICollectionView = {
        var flowLayout = UICollectionViewFlowLayout()
        let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: flowLayout)
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "collectionCell")
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.backgroundColor = UIColor.cyan
        return collectionView
    }()

    override func loadView() {
        super.loadView()
        self.view.addSubview(collectionView)
    }

    override func viewDidLoad() {
        super.viewDidLoad()     
        setupConstraints()   
    }

    override func viewDidAppear(_ animated: Bool) {
        print("123: \(collectionView.contentSize.height)")
        //prints 47 for this particular example, however doing snp.makeConstraints
        //(or remakeConstraints) from here doesn't make any difference
        //setupConstraints()
    }

    func configure(categories:[String]) {
        self.categories = categories
        for category in categories {
            let button = BorderedButton()
            button.setTitle(category, for: .normal)
            buttons.append(button)
        }        
    }


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


    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
    {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath)
        cell.addSubview(buttons[indexPath.row])

        //prints 47 for this particular example, however doing snp.makeConstraints
        //(or remakeConstraints) from here doesn't make any difference
        print("XYZ: \(collectionView.contentSize.height)")
        return cell
    }

    func collectionView(_ collectionView: UICollectionView,
                                 layout collectionViewLayout: UICollectionViewLayout,
                                 sizeForItemAt indexPath: IndexPath) -> CGSize
    {
        return buttons[indexPath.row].intrinsicContentSize
    }

    private func setupConstraints() {
        collectionView.snp.makeConstraints { (make) in
            make.left.top.equalToSuperview()
            make.width.equalTo(300)


            print("ABC: \(collectionView.contentSize.height)")

            //this does not work since height=0
            make.height.equalTo(collectionView.contentSize.height)

            //this works fine
            make.height.equalTo(47)
        }
    }

}  

Thanks!

EDIT: Added missing class

Thanks for the reply below. It indeed works, and great you could get the question even though I missed to include half of the code :-) Below is the missing piece, it is the class using the CategoryList. With your help I get the CategoryList to expand dynamically to the correct height, which is excellent. However, it messes up the placement of the surrounding views (view1 and view2 below). I suspect it has something to do with the

make.height.equalTo(categoryList.collectionView.snp.height)

line, maybe it should just reference the collectionView constraint property instead, but I have no idea how that should (or even could) be done?

class PlaylistDetailsHeaderView : UITableViewCell {

    private var categoryList:CategoryList

    private(set) lazy var view1: UIView = {
        let view = UIView()
        return view
    }()

    private(set) lazy var view2: UIView = {
        let view = UIView()
        return view
    }()


    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {

        self.categoryList = CategoryList()

        super.init(style: style, reuseIdentifier: reuseIdentifier)

        self.contentView.addSubview(view1)
        self.contentView.addSubview(view2)
    }

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

    func configure() {

        let categories = ["alfa", "beta", "gamma", "fkdiii", "keoooe", "dfdsje", "jkjfldsjflsd", "alfa", "beta", "gamma", "fkdiii", "keoooe", "dfdsje", "jkjfldsjflsd"]

        categoryList.configure(categories: categories)
        self.contentView.addSubview(categoryList.view)

        setupConstraints()

    }

    private func setupConstraints() {

        view1.snp.makeConstraints { make in
            make.left.top.equalToSuperview()
        } 

        categoryList.view.snp.makeConstraints{ make in
            make.left.equalToSuperview()
            make.top.equalTo(view1.snp.bottom).offset(20)
            make.width.equalTo(300)

            make.height.equalTo(categoryList.collectionView.snp.height)   
        }

        view2.snp.makeConstraints { make in
            make.top.equalTo(categoryList.view.snp.bottom).offset(20)
        }
    }
}
2 Answers

Vollan's answer is quite close. Just ensure that you're on the main thread when updating the constraint. So summing up:

var collectionViewHeightConstraint: Constraint? // on top as class property 

Then inside the snapkit block:

collectionView.snp.makeConstraints { make in
    make.top.equalToSuperview() //example
    collectionViewHeightConstraint = make.height.equalTo(self.collectionView.contentSize.height).constraint
}

And last but not least, when data gets updated, don't forget:

DispatchQueue.main.async {
    self.collectionViewHeightConstraint?.update(offset: self.collectionView.contentSize.height)
}
Related