Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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)
        }
    }
}
like image 268
jola Avatar asked May 22 '17 13:05

jola


2 Answers

If i understand what you meant this is how i think you should do it.

First you need to store the height somewhere. That will be defaulted to 0, or in your case 47.

var heightConstraint: Constraint?

Then you will set the constraints in snapkit as normal, but when you set the height constraint you store it inside the previously created variable. Don't forget to add ".constraint".

heightConstraint = make.height.equalTo(47).constraint

Next step is when the collectionView is populated, you can use the heightConstraint again (if you made the variable reachable)

heightConstraint.updateOffset(collectionView.contentSize.height)
like image 82
Vollan Avatar answered Nov 17 '22 10:11

Vollan


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)
}
like image 2
Bruno Cunha Avatar answered Nov 17 '22 09:11

Bruno Cunha