33

For UICollectionView's dynamic height cells we use,

if let layout = self.collectionViewLayout as? UICollectionViewFlowLayout {
    layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
}

with the proper constraint of height and width, it works fine with iOS 11.* versions but it breaks and does not make the cells dynamic for iOS 12.0

Aman Gupta
  • 985
  • 1
  • 8
  • 18
  • Maybe related: [In iOS 12, when does the UICollectionView layout cells, use autolayout in nib](https://stackoverflow.com/q/51375566/1033581). In particular, my answer with AutoLayoutCollectionView may help. – Cœur Sep 04 '18 at 02:05
  • 2
    I figured out: In iOS 12 estimatedItemSize is compiled and added as a size constraint. It conflicts with other specified constraints. ale84's solution works for me, but there're also conflict constraints showed up on the console. My personal experience with collection view is that every OS version there're some bugs here and there, very annoying. – X.Y. Sep 26 '18 at 15:16

5 Answers5

68

In my case, I solved this by explicitly adding the following constraints to the cell's contentView.

class Cell: UICollectionViewCell {
    // ...

    override func awakeFromNib() {
        super.awakeFromNib()

        // Addresses a separate issue and prevent auto layout warnings due to the temporary width constraint in the xib.
        contentView.translatesAutoresizingMaskIntoConstraints = false

        // Code below is needed to make the self-sizing cell work when building for iOS 12 from Xcode 10.0:
        let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor)
        let rightConstraint = contentView.rightAnchor.constraint(equalTo: rightAnchor)
        let topConstraint = contentView.topAnchor.constraint(equalTo: topAnchor)
        let bottomConstraint = contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
        NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint])
    }
}

These constraints are already in place inside the xib of the cell, but somehow they aren't enough for iOS 12.

The other threads that suggested calling collectionView.collectionViewLayout.invalidateLayout() in various places didn't help in my situation.

Sample code here: https://github.com/larrylegend/CollectionViewAutoSizingTest

This applies the workaround to code from a tutorial by https://medium.com/@wasinwiwongsak/uicollectionview-with-autosizing-cell-using-autolayout-in-ios-9-10-84ab5cdf35a2:

Larry
  • 421
  • 5
  • 8
ale84
  • 1,406
  • 12
  • 16
  • 2
    Thanks, had the same issue, this worked for me for now. And as well I'm invoking this code only for iOS12: if #available(iOS 12.0, *) { } – Naloiko Eugene Sep 19 '18 at 09:24
  • 1
    It's working. Thank you, You have saved my days men. – Ravi Dhorajiya Sep 20 '18 at 03:40
  • 7
    How did you ever figure this out? I don't think Apple mentioned this huge breaking change anywhere. It's sad that they make us pollute our app code with boilerplate garbage like this. – Andrew Koster Sep 21 '18 at 22:43
  • @AndrewKoster an intuition and a lucky guess. I think this code is just a workaround and Apple should fix this. – ale84 Sep 24 '18 at 13:54
  • Amazing. just what i needed – Phil Sep 28 '18 at 18:57
  • Thanks @ale84, +100 for this answer. You really saved my day – Mansuu.... Oct 24 '18 at 09:23
  • Yes, it worked like charm. This answer should be the accepted answer for this question. And I second @ale84 as this bug should be addressed by Apple. – Yassine ElBadaoui Apr 27 '19 at 03:24
  • Hi @ale84, The code works great when I use it to load the cell with dynamic height. However, when I reload the same collection view using `invalidateLayout` or `reloadData` method, the height of the visible cell become usual maximum height. Once I scroll view, things goes to normal. Have you encountered any such issue? Any help would be highly appreciated. Thanks. Sample source code can be found below: https://github.com/mysticvalley/CollectionViewAutoResizeiOS12Issue – Rajan Maharjan May 20 '19 at 12:58
  • 4
    @RajanMaharjan Hi, from looking at your sample, I think you should set the `itemSize` of the layout to `UICollectionViewFlowLayout.automaticSize`, and the `estimatedItemSize` to an estimated value. – ale84 May 21 '19 at 07:39
  • @ale84, I already got it working after updating the `itemSize` to `.automaticSize`. Thanks a bunch. :) – Rajan Maharjan May 21 '19 at 11:59
  • Wow I wondered why every UICollectionView tutorial on the internet doesn't work. Amazingly, this helped. – astreltsov Jul 04 '19 at 12:06
  • Amazing but it works. The same story bud shorter is: contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] – HotJard Aug 21 '19 at 16:34
10

Based on ale84's answer and because of the fact I needed that iOS 12 fix in multiple places I created a UICollectionViewCell extension which I named UICollectionViewCell+iOS12:

extension UICollectionViewCell {
    /// This is a workaround method for self sizing collection view cells which stopped working for iOS 12
    func setupSelfSizingForiOS12(contentView: UIView) {
        contentView.translatesAutoresizingMaskIntoConstraints = false
        let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor)
        let rightConstraint = contentView.rightAnchor.constraint(equalTo: rightAnchor)
        let topConstraint = contentView.topAnchor.constraint(equalTo: topAnchor)
        let bottomConstraint = contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
        NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint])
    }
}

And then in your collection view cells we do something like this (if your cell is created in IB):

override func awakeFromNib() {
    super.awakeFromNib()
    if #available(iOS 12, *) { setupSelfSizingForiOS12(contentView: contentView) }
}
Vasil Garov
  • 4,851
  • 1
  • 26
  • 37
1

Objective C version of above answer:

-(void)awakeFromNib{
    [super awakeFromNib];

    if (@available(iOS 12.0, *)) {
        // Addresses a separate issue and prevent auto layout warnings due to the temporary width constraint in the xib.
        self.contentView.translatesAutoresizingMaskIntoConstraints = NO;

        // Code below is needed to make the self-sizing cell work when building for iOS 12 from Xcode 10.0:

        NSLayoutConstraint *leftConstraint = [self.contentView.leftAnchor constraintEqualToAnchor:self.leftAnchor constant:0];
        NSLayoutConstraint *rightConstraint = [self.contentView.rightAnchor constraintEqualToAnchor:self.rightAnchor constant:0];
        NSLayoutConstraint *topConstraint = [self.contentView.topAnchor constraintEqualToAnchor:self.topAnchor constant:0];
        NSLayoutConstraint *bottomConstraint = [self.contentView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:0];

        [NSLayoutConstraint activateConstraints:@[leftConstraint, rightConstraint, topConstraint, bottomConstraint]];
    }

}

For Objective-C lovers like me ;) cheers !!!

Harish Pathak
  • 1,567
  • 1
  • 18
  • 32
-1

i have the same problem. using UILabels to size the collectionview. if i run collectionView.collectionViewLayout.invalidateLayout() before reloading the data it does an ok job of sizing the labels.

still not quite right. it's tough for me to figure because i'm running it on the simulator vs. device.

CDM
  • 285
  • 3
  • 11
-1

I managed to solve this problem by putting such code into my subclass of UICollectionViewCell

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    [self updateConstraintsIfNeeded];
    CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(1000, 1000)];

    CGRect alteredRect = layoutAttributes.frame;
    alteredRect.size = size;
    layoutAttributes.frame = alteredRect;
    return layoutAttributes;
}

and subclassing UICollectionView like this

@interface CustomCollectionView ()
@property (nonatomic) BOOL shouldInvalidateLayout;
@end

@implementation CustomCollectionView

- (void)layoutSubviews {
    [super layoutSubviews];
    if (self.shouldInvalidateLayout) {
        [self.collectionViewLayout invalidateLayout];
        self.shouldInvalidateLayout = NO;
    }
}

- (void)reloadData {
    self.shouldInvalidateLayout = YES;
    [super reloadData];
}

@end