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
In one bizarre case this
contentView.translatesAutoresizingMaskIntoConstraints = false
would not work. Added four explicit anchors to the contentView and it worked.
class AnnoyingCell: UICollectionViewCell {
@IBOutlet var word: UILabel!
override init(frame: CGRect) {
super.init(frame: frame); common() }
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder); common() }
private func common() {
contentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentView.leftAnchor.constraint(equalTo: leftAnchor),
contentView.rightAnchor.constraint(equalTo: rightAnchor),
contentView.topAnchor.constraint(equalTo: topAnchor),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
}
and as usual
estimatedItemSize = UICollectionViewFlowLayout.automaticSize
in YourLayout: UICollectionViewFlowLayout
Who knows? Might help someone.
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.
If you implement UICollectionViewDelegateFlowLayout method:
- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath
When you call collectionview performBatchUpdates:completion:
, the size height will use sizeForItemAtIndexPath
instead of
preferredLayoutAttributesFittingAttributes
.
The rendering process of performBatchUpdates:completion
will go through the method preferredLayoutAttributesFittingAttributes
but it ignores your changes.
Update more information:
If you use flowLayout.estimatedItemSize
, suggest use iOS8.3 later version. Before iOS8.3, it will crash [super layoutAttributesForElementsInRect:rect];
.
The error message is
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
Second, in iOS8.x version, flowLayout.estimatedItemSize
will cause different section inset setting did not work. i.e. function: (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:
.
In addition to above answers,
Just make sure you set estimatedItemSize property of UICollectionViewFlowLayout to some size and do not implement sizeForItem:atIndexPath delegate method.
That's it.
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:
label.preferredMaxLayoutWidth = 200
More info: here
Cheers!
I tried using estimatedItemSize
but there were a bunch of bugs when inserting and deleting cells if the estimatedItemSize
was not exactly equal to the cell's height. i stopped setting estimatedItemSize
and implemented dynamic cell's by using a prototype cell. here's how that's done:
create this protocol:
protocol SizeableCollectionViewCell {
func fittedSize(forConstrainedSize size: CGSize)->CGSize
}
implement this protocol in your custom UICollectionViewCell
:
class YourCustomCollectionViewCell: UICollectionViewCell, SizeableCollectionViewCell {
@IBOutlet private var mTitle: UILabel!
@IBOutlet private var mDescription: UILabel!
@IBOutlet private var mContentView: UIView!
@IBOutlet private var mTitleTopConstraint: NSLayoutConstraint!
@IBOutlet private var mDesciptionBottomConstraint: NSLayoutConstraint!
func fittedSize(forConstrainedSize size: CGSize)->CGSize {
let fittedSize: CGSize!
//if height is greatest value, then it's dynamic, so it must be calculated
if size.height == CGFLoat.greatestFiniteMagnitude {
var height: CGFloat = 0
/*now here's where you want to add all the heights up of your views.
apple provides a method called sizeThatFits(size:), but it's not
implemented by default; except for some concrete subclasses such
as UILabel, UIButton, etc. search to see if the classes you use implement
it. here's how it would be used:
*/
height += mTitle.sizeThatFits(size).height
height += mDescription.sizeThatFits(size).height
height += mCustomView.sizeThatFits(size).height //you'll have to implement this in your custom view
//anything that takes up height in the cell has to be included, including top/bottom margin constraints
height += mTitleTopConstraint.constant
height += mDescriptionBottomConstraint.constant
fittedSize = CGSize(width: size.width, height: height)
}
//else width is greatest value, if not, you did something wrong
else {
//do the same thing that's done for height but with width, remember to include leading/trailing margins in calculations
}
return fittedSize
}
}
now make your controller conform to UICollectionViewDelegateFlowLayout
, and in it, have this field:
class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout {
private var mCustomCellPrototype = UINib(nibName: <name of the nib file for your custom collectionviewcell>, bundle: nil).instantiate(withOwner: nil, options: nil).first as! SizeableCollectionViewCell
}
it will be used as a prototype cell to bind data to and then determine how that data affected the dimension that you want to be dynamic
finally, the UICollectionViewDelegateFlowLayout's
collectionView(:layout:sizeForItemAt:)
has to be implemented:
class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
private var mDataSource: [CustomModel]
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath)->CGSize {
//bind the prototype cell with the data that corresponds to this index path
mCustomCellPrototype.bind(model: mDataSource[indexPath.row]) //this is the same method you would use to reconfigure the cells that you dequeue in collectionView(:cellForItemAt:). i'm calling it bind
//define the dimension you want constrained
let width = UIScreen.main.bounds.size.width - 20 //the width you want your cells to be
let height = CGFloat.greatestFiniteMagnitude //height has the greatest finite magnitude, so in this code, that means it will be dynamic
let constrainedSize = CGSize(width: width, height: height)
//determine the size the cell will be given this data and return it
return mCustomCellPrototype.fittedSize(forConstrainedSize: constrainedSize)
}
}
and that's it. Returning the cell's size in collectionView(:layout:sizeForItemAt:)
in this way preventing me from having to use estimatedItemSize
, and inserting and deleting cells works perfectly.