Uicollectionviewcell Dynamic Height W/Two Dynamic Labels & Auto Layout

UICollectionViewCell dynamic height w/two dynamic labels & auto layout

flowLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
flowLayout.itemSize = UICollectionViewFlowLayoutAutomaticSize

Of course I am familiar with Apple's repeated claims that a UICollectionViewCell in a UICollectionViewFlowLayout can be self-sizing based on internal constraints. But I have never seen working code from Apple that demonstrated this feature, and I have never once myself succeeded in confirming those claims to be true. I do not believe there is really such a thing as a self-sizing cell. For years, trying to get a cell to be self-sizing resulted in crashes. In iOS 10, the crashing mostly stopped, but now the flow layout was not laying out the cells correctly (which sounds like what you're seeing).

Naturally, I have filed bugs on all this, year after year.

Meanwhile, what I do is to give the collection view a delegate and implement collectionView(_:layout:sizeForItemAt:), and I suggest that you should do the same.


EDIT New in iOS 13 you can use a composable layout and abandon UICollectionViewFlowLayout altogether. Self-sizing cells work fine in a composable layout.

UICollectionView, full width cells, allow autolayout dynamic height?

1. Solution for iOS 13+

With Swift 5.1 and iOS 13, you can use Compositional Layout objects in order to solve your problem.

The following complete sample code shows how to display multiline UILabel inside full-width UICollectionViewCell:

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

let items = [
[
"Lorem ipsum dolor sit amet.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
],
[
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
],
[
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
]
]

override func viewDidLoad() {
super.viewDidLoad()

let size = NSCollectionLayoutSize(
widthDimension: NSCollectionLayoutDimension.fractionalWidth(1),
heightDimension: NSCollectionLayoutDimension.estimated(44)
)
let item = NSCollectionLayoutItem(layoutSize: size)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1)

let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
section.interGroupSpacing = 10

let headerFooterSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(40)
)
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerFooterSize,
elementKind: "SectionHeaderElementKind",
alignment: .top
)
section.boundarySupplementaryItems = [sectionHeader]

let layout = UICollectionViewCompositionalLayout(section: section)
collectionView.collectionViewLayout = layout
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
}

override func numberOfSections(in collectionView: UICollectionView) -> Int {
return items.count
}

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items[section].count
}

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.label.text = items[indexPath.section][indexPath.row]
return cell
}

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
headerView.label.text = "Header"
return headerView
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { context in
self.collectionView.collectionViewLayout.invalidateLayout()
}, completion: nil)
}

}

HeaderView.swift

import UIKit

class HeaderView: UICollectionReusableView {

let label = UILabel()

override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .magenta

addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}

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

}

CustomCell.swift

import UIKit

class CustomCell: UICollectionViewCell {

let label = UILabel()

override init(frame: CGRect) {
super.init(frame: frame)

label.numberOfLines = 0
backgroundColor = .orange
contentView.addSubview(label)

label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}

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

}

Expected display:

Sample Image


2. Solution for iOS 11+

With Swift 5.1 and iOS 11, you can subclass UICollectionViewFlowLayout and set its estimatedItemSize property to UICollectionViewFlowLayout.automaticSize (this tells the system that you want to deal with autoresizing UICollectionViewCells). You'll then have to override layoutAttributesForElements(in:) and layoutAttributesForItem(at:) in order to set cells width. Lastly, you'll have to override your cell's preferredLayoutAttributesFitting(_:) method and compute its height.

The following complete code shows how to display multiline UILabel inside full-width UIcollectionViewCell (constrained by UICollectionView's safe area and UICollectionViewFlowLayout's insets):

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

let items = [
[
"Lorem ipsum dolor sit amet.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
],
[
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
],
[
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
]
]
let customFlowLayout = CustomFlowLayout()

override func viewDidLoad() {
super.viewDidLoad()

customFlowLayout.sectionInsetReference = .fromContentInset // .fromContentInset is default
customFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
customFlowLayout.minimumInteritemSpacing = 10
customFlowLayout.minimumLineSpacing = 10
customFlowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
customFlowLayout.headerReferenceSize = CGSize(width: 0, height: 40)

collectionView.collectionViewLayout = customFlowLayout
collectionView.contentInsetAdjustmentBehavior = .always
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
}

override func numberOfSections(in collectionView: UICollectionView) -> Int {
return items.count
}

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items[section].count
}

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.label.text = items[indexPath.section][indexPath.row]
return cell
}

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
headerView.label.text = "Header"
return headerView
}

}

CustomFlowLayout.swift

import UIKit

final class CustomFlowLayout: UICollectionViewFlowLayout {

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
layoutAttributesObjects?.forEach({ layoutAttributes in
if layoutAttributes.representedElementCategory == .cell {
if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
layoutAttributes.frame = newFrame
}
}
})
return layoutAttributesObjects
}

override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let collectionView = collectionView else {
fatalError()
}
guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
return nil
}

layoutAttributes.frame.origin.x = sectionInset.left
layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right
return layoutAttributes
}

}

HeaderView.swift

import UIKit

class HeaderView: UICollectionReusableView {

let label = UILabel()

override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .magenta

addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}

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

}

CustomCell.swift

import UIKit

class CustomCell: UICollectionViewCell {

let label = UILabel()

override init(frame: CGRect) {
super.init(frame: frame)

label.numberOfLines = 0
backgroundColor = .orange
contentView.addSubview(label)

label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}

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

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
layoutIfNeeded()
layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
return layoutAttributes
}

}

Here are some alternative implementations for preferredLayoutAttributesFitting(_:):

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
return layoutAttributes
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
label.preferredMaxLayoutWidth = layoutAttributes.frame.width
layoutAttributes.frame.size.height = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
return layoutAttributes
}

Expected display:

Sample Image

Is it possible to create cells with dynamic width but fixed height using Compositional Layout

You can build the "section 2" layout using UICollectionViewCompositionalLayout. Here's how:

let layoutSize = NSCollectionLayoutSize(
widthDimension: .estimated(100),
heightDimension: .absolute(32)
)

let group = NSCollectionLayoutGroup.horizontal(
layoutSize: .init(
widthDimension: .fractionalWidth(1.0),
heightDimension: layoutSize.heightDimension
),
subitems: [.init(layoutSize: layoutSize)]
)
group.interItemSpacing = .fixed(8)

let section = NSCollectionLayoutSection(group: group)
section.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
section.interGroupSpacing = 8

return .init(section: section)

The important part is that the group takes up the available width using .fractionalWidth(1.0), but the subitems have an estimated width. Just make sure your collection view cell can be self-sized by overriding sizeThatFits(_:), preferredLayoutAttributesFitting(_:) or using Auto Layout. The result will be a layout that looks like this:

Sample Image

To use "section 1" and "section 2" in the same UICollectionViewCompositionalLayout, just create your layout using init(sectionProvider:), where you return the appropriate NSCollectionLayoutSection for the current section identifier:

func createCollectionViewLayout() -> UICollectionViewCompositionalLayout {
.init(sectionProvider: { [weak self] sectionIndex, _ in
guard let sectionIdentifier = self?.dataSource.snapshot().sectionIdentifiers[sectionIndex] else { return nil }
switch sectionIdentifier {
case .section1: return self?.createSection1()
case .section2: return self?.createSection2()
}
})
}

It's worth noting you may see some odd behavior with this layout if your view controller is presented using the default pageSheet style on iOS 13+. I have a related question about that here (using a slightly different layout).

Height of UICollectionViewCell with 2 multiline UILabels / self sizing collection view cells

Okay it seems that I misunderstood how to use sizeThatFits. I now went with a different approach, though. This is what I did to get my self-sizing cells:

  • I put the three labels in a vertical UIStackView
  • I put the UIImageView and the UIStackView in a horizontal UIStackView
  • I pinned to parent stack view to left, right, top and bottom of the cell where the bottom constraint is >= 0 instead of just = 0
  • I use a protocol in my UICollectionViewLayout to ask for the height of a given width
  • In my UICollectionViewController, I implement the protocol and where I register my cells, I also instantiate one of the cells which I use for measuring in the protocol function
  • In the protocol, I first get the model object, then I hand down the model to my measuring cell which I keep in an instance variable in my view controller
  • I then call getHeight(forWidth) on my cell. In this function, I set the cells frame.size.height to a very large number. Then I use AutoLayout to calculate the actual height (setNeedsDisplay(), layoutIfNeeded())
  • The height I need is the height of the parent stack view plus the margings

I hope this helps somebody like me in the future. I also wrote a small blog post with some code samples of what I'm doing.

UICollectionView - dynamic cell height?

Here is a Ray Wenderlich tutorial that shows you how to use AutoLayout to dynamically size UITableViewCells. I would think it would be the same for UICollectionViewCell.

Basically, though, you end up dequeueing and configuring a prototype cell and grabbing its height. After reading this article, I decided to NOT implement this method and just write some clear, explicit sizing code.

Here's what I consider the "secret sauce" for the entire article:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self heightForBasicCellAtIndexPath:indexPath];
}

- (CGFloat)heightForBasicCellAtIndexPath:(NSIndexPath *)indexPath {
static RWBasicCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWBasicCellIdentifier];
});

[self configureBasicCell:sizingCell atIndexPath:indexPath];
return [self calculateHeightForConfiguredSizingCell:sizingCell];
}

- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell {
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];

CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height + 1.0f; // Add 1.0f for the cell separator height
}


EDIT: I did some research into your crash and decided that there is no way to get this done without a custom XIB. While that is a bit frustrating, you should be able to cut and paste from your Storyboard to a custom, empty XIB.

Once you've done that, code like the following will get you going:

//  ViewController.m
#import "ViewController.h"
#import "CollectionViewCell.h"
@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout> {

}
@property (weak, nonatomic) IBOutlet CollectionViewCell *cell;
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
[self.collectionView registerNib:[UINib nibWithNibName:@"CollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"cell"];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"viewDidAppear...");
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 50;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 10.0f;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 10.0f;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return [self sizingForRowAtIndexPath:indexPath];
}
- (CGSize)sizingForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *title = @"This is a long title that will cause some wrapping to occur. This is a long title that will cause some wrapping to occur.";
static NSString *subtitle = @"This is a long subtitle that will cause some wrapping to occur. This is a long subtitle that will cause some wrapping to occur.";
static NSString *buttonTitle = @"This is a really long button title that will cause some wrapping to occur.";
static CollectionViewCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [[NSBundle mainBundle] loadNibNamed:@"CollectionViewCell" owner:self options:nil][0];
});
[sizingCell configureWithTitle:title subtitle:[NSString stringWithFormat:@"%@: Number %d.", subtitle, (int)indexPath.row] buttonTitle:buttonTitle];
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize cellSize = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
NSLog(@"cellSize: %@", NSStringFromCGSize(cellSize));
return cellSize;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *title = @"This is a long title that will cause some wrapping to occur. This is a long title that will cause some wrapping to occur.";
static NSString *subtitle = @"This is a long subtitle that will cause some wrapping to occur. This is a long subtitle that will cause some wrapping to occur.";
static NSString *buttonTitle = @"This is a really long button title that will cause some wrapping to occur.";
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
[cell configureWithTitle:title subtitle:[NSString stringWithFormat:@"%@: Number %d.", subtitle, (int)indexPath.row] buttonTitle:buttonTitle];
return cell;
}
@end

The code above (along with a very basic UICollectionViewCell subclass and associated XIB) gives me this:

Sample Image

How to dynamically size CollectionViewCell as per UILabel

You don't need to calculate the size of the strings that you want to display. Do the following:

  1. Set collectionView's height equal to the expected height of tags.
  2. Set tagLabel's leading, trailing, top and bottom constraints equal to the corresponding cell's contentView constraints.
  3. Remove your implementation of func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize. The cell size will be calculated automatically if you don't use a custom value for estimatedItemSize. If you want to be sure, add (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.estimatedItemSize = UICollectionViewFlowLayout.automaticSize to the function where you setup all views.

You will see the following result:

Running on iPhone 11 Pro Max simulator

UICollectionView Self Sizing Cells with Auto Layout

This answer is outdated from iOS 14 with the addition of compositional layouts. Please consider updating the new API

Updated for Swift 5

preferredLayoutAttributesFittingAttributes renamed to preferredLayoutAttributesFitting and use auto sizing



Updated for Swift 4

systemLayoutSizeFittingSize renamed to systemLayoutSizeFitting



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 on UICollectionViewFlowLayout

Flow layout will become dynamic in nature once you set the estimatedItemSize property.

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

#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 cell

When 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:

//forces the system to do one layout pass
var isHeightCalculated: Bool = false

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
//Exhibit A - We need to cache our calculation to prevent a crash.
if !isHeightCalculated {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var newFrame = layoutAttributes.frame
newFrame.size.width = CGFloat(ceilf(Float(size.width)))
layoutAttributes.frame = newFrame
isHeightCalculated = true
}
return layoutAttributes
}

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.



Related Topics



Leave a reply



Submit