I'm trying to get self sizing UICollectionViewCells
working with Auto Layout, but I can't seem to get the cells to size themselves to the content. I'm having trouble understanding how the cell's size is updated from the contents of what's inside the cell's contentView.
Here's the setup I've tried:
- Custom
UICollectionViewCell
with aUITextView
in its contentView. - Scrolling for the
UITextView
is disabled. - The contentView's horizontal constraint is: "H:|[_textView(320)]", i.e. the
UITextView
is pinned to the left of the cell with an explicit width of 320. - The contentView's vertical constraint is: "V:|-0-[_textView]", i.e. the
UITextView
pinned to the top of the cell. - The
UITextView
has a height constraint set to a constant which theUITextView
reports will fit the text.
Here's what it looks like with the cell background set to red, and the UITextView
background set to Blue:
I put the project that I've been playing with on GitHub here.
Updated for Swift 5
preferredLayoutAttributesFittingAttributes
renamed topreferredLayoutAttributesFitting
and use auto sizingUpdated for Swift 4
systemLayoutSizeFittingSize
renamed tosystemLayoutSizeFitting
Updated for iOS 9
After seeing my GitHub solution break under iOS 9 I finally got the time to investigate the issue fully. I have now updated the repo to include several examples of different configurations for self sizing cells. My conclusion is that self sizing cells are great in theory but messy in practice. A word of caution when proceeding with self sizing cells.
TL;DR
Check out my GitHub project
Self sizing cells are only supported with flow layout so make sure thats what you are using.
There are two things you need to setup for self sizing cells to work.
1. Set
estimatedItemSize
onUICollectionViewFlowLayout
Flow layout will become dynamic in nature once you set the
estimatedItemSize
property.2. Add support for sizing on your cell subclass
This comes in 2 flavours; Auto-Layout or custom override of
preferredLayoutAttributesFittingAttributes
.Create and configure cells with Auto Layout
I won't go to in to detail about this as there's a brilliant SO post about configuring constraints for a cell. Just be wary that Xcode 6 broke a bunch of stuff with iOS 7 so, if you support iOS 7, you will need to do stuff like ensure the autoresizingMask is set on the cell's contentView and that the contentView's bounds is set as the cell's bounds when the cell is loaded (i.e.
awakeFromNib
).Things you do need to be aware of is that your cell needs to be more seriously constrained than a Table View Cell. For instance, if you want your width to be dynamic then your cell needs a height constraint. Likewise, if you want the height to be dynamic then you will need a width constraint to your cell.
Implement
preferredLayoutAttributesFittingAttributes
in your custom cellWhen this function is called your view has already been configured with content (i.e.
cellForItem
has been called). Assuming your constraints have been appropriately set you could have an implementation like this:NOTE On iOS 9 the behaviour changed a bit that could cause crashes on your implementation if you are not careful (See more here). When you implement
preferredLayoutAttributesFittingAttributes
you need to ensure that you only change the frame of your layout attributes once. If you don't do this the layout will call your implementation indefinitely and eventually crash. One solution is to cache the calculated size in your cell and invalidate this anytime you reuse the cell or change its content as I have done with theisHeightCalculated
property.Experience your layout
At this point you should have 'functioning' dynamic cells in your collectionView. I haven't yet found the out-of-the box solution sufficient during my tests so feel free to comment if you have. It still feels like
UITableView
wins the battle for dynamic sizing IMHO.Caveats
Be very mindful that if you are using prototype cells to calculate the estimatedItemSize - this will break if your XIB uses size classes. The reason for this is that when you load your cell from a XIB its size class will be configured with
Undefined
. This will only be broken on iOS 8 and up since on iOS 7 the size class will be loaded based on the device (iPad = Regular-Any, iPhone = Compact-Any). You can either set the estimatedItemSize without loading the XIB, or you can load the cell from the XIB, add it to the collectionView (this will set the traitCollection), perform the layout, and then remove it from the superview. Alternatively you could also make your cell override thetraitCollection
getter and return the appropriate traits. It's up to you.Let me know if I missed anything, hope I helped and good luck coding
In iOS 10+ this is a very simple 2 step process.
Ensure that all your cell contents are placed within a single UIView (or inside a descendant of UIView like UIStackView which simplifies autolayout a lot). Just like with dynamically resizing UITableViewCells, the whole view hierarchy needs to have constraints configured, from the outermost container to the innermost view. That includes constraints between the UICollectionViewCell and the immediate childview
Instruct the flowlayout of your UICollectionView to size automatically
For anyone who tried everything without luck, this is the only thing that got it working for me. For the multiline labels inside cell, try adding this magic line:
More info: here
Cheers!
I did a dynamic cell height of collection view. Here is git hub repo.
And, dig out why preferredLayoutAttributesFittingAttributes is called more than once. Actually, it will be called at least 3 times.
The console log picture :
1st preferredLayoutAttributesFittingAttributes:
The layoutAttributes.frame.size.height is current status 57.5.
2nd preferredLayoutAttributesFittingAttributes:
The cell frame height changed to 534.5 as our expected. But, the collection view still zero height.
3rd preferredLayoutAttributesFittingAttributes:
You can see the collection view height was changed from 0 to 477.
The behavior is similar to handle scroll:
At beginning, I thought this method only call once. So I coded as the following:
This line:
will cause system call infinite loop and App crash.
Any size changed, it will validate all cells' preferredLayoutAttributesFittingAttributes again and again until every cells' positions (i.e frames) are no more change.
contentView anchor mystery:
In one bizarre case this
would not work. Added four explicit anchors to the contentView and it worked.
and as usual
in
YourLayout: UICollectionViewFlowLayout
Who knows? Might help someone.
Credit
https://www.vadimbulavin.com/collection-view-cells-self-sizing/
stumbled on to the tip there - never saw it anywhere else in all the 1000s articles on this.
A few key changes to Daniel Galasko's answer fixed all my problems. Unfortunately, I don't have enough reputation to comment directly (yet).
In step 1, when using Auto Layout, simply add a single parent UIView to the cell. EVERYTHING inside the cell must be a subview of the parent. That answered all of my problems. While Xcode adds this for UITableViewCells automatically, it doesn't (but it should) for UICollectionViewCells. According to the docs:
Then skip step 3 entirely. It isn't needed.