4

I have a storyboard, consisting of a single UICollectionView with multiple cells, each of varying height. The first cell takes the height from the UICollectionViewDelegateFlowLayout:

func collectionView(collectionView: UICollectionView,
        layout collectionViewLayout: UICollectionViewLayout,
        sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize

.. but I'd like the second cell to be shorter. I've placed two UIStackViews inside a "master" UIStackView inside the cell, and each of the inner UIStackViews has one or more labels, like this:

cell
--> stackView (master)
    --> stackView (1)
        --> label
    --> stackView (2)
        --> label
        --> label (etc)

.. in the hope that the UIStackView would make the cell height dynamic, but it doesn't. It takes the height from the UICollectionViewDelegateFlowLayout as before.

How should I be doing this?

coco
  • 2,998
  • 1
  • 35
  • 58

2 Answers2

2

You need to compute the size of the content for the CollectionViewCell and return it to the sizeForItemAt function.

func collectionView(_ collectionView: UICollectionView,
                    layout collectionViewLayout: UICollectionViewLayout,
                    sizeForItemAt indexPath: IndexPath) -> CGSize {

    // Create an instance of the `FooCollectionViewCell`, either from nib file or from code.
    // Here we assume `FooCollectionViewCell` is created from a FooCollectionViewCell.xib
    let cell: FooCollectionViewCell = UINib(nibName: "FooCollectionViewCell", bundle: nil)
        .instantiate(withOwner: nil, options: nil)
        .first as! FooCollectionViewCell

    // Configure the data for your `FooCollectionViewCell`
    cell.stackView.addArrangedSubview(/*view1*/)
    cell.stackView.addArrangedSubview(/*view2*/)

    // Layout the collection view cell
    cell.setNeedsLayout()
    cell.layoutSubviews()

    // Calculate the height of the collection view based on the content
    let size = cell.contentView.systemLayoutSizeFitting(
        CGSize(width: collectionView.bounds.width, height: 0),
        withHorizontalFittingPriority: UILayoutPriorityRequired,
        verticalFittingPriority: UILayoutPriorityFittingSizeLevel)

    return size
}

With this, you will have a dynamic cell heights UICollectionView.


Further notes:

  1. for configuration of the collection view cell, you can create a helper function func configure(someData: SomeData) on FooCollectionViewCell so that the code could be shared between the cellForItemAt function and sizeForItemAt function.

    // Configure the data for your `FooCollectionViewCell`
    cell.stackView.addArrangedSubview(/*view1*/)
    cell.stackView.addArrangedSubview(/*view2*/)
    
  2. For these two lines of code, it seems only needed if the UICollectionViewCell contains vertical UIStackView as subViews (likely a bug from Apple).

    // Layout the collection view cell
    cell.setNeedsLayout()
    cell.layoutSubviews()
    
Crashalot
  • 33,605
  • 61
  • 269
  • 439
Yuchen
  • 30,852
  • 26
  • 164
  • 234
1

If you'd like to change the height of your cells you're going to have to change the height you return in the sizeForItemAtIndexPath. The stack views aren't going to have any effect here. Here's an example of what you can do:

func collectionView(collectionView: UICollectionView,
    layout collectionViewLayout: UICollectionViewLayout,
    sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {

    if indexPath.row == 1 {
        return CGSizeMake(width, height/2)
    }
    return  CGSizeMake(width, height)
}

This will change the size of your cells at row 1. You can also use indexPath.section to choose sections. Hope this helps.

Rob Norback
  • 6,401
  • 2
  • 34
  • 38