49

My UICollectionView cells contain UILabels with multiline text. I don't know the height of the cells until the text has been set on the label.

-(CGSize)collectionView:(UICollectionView *)collectionView
                 layout:(UICollectionViewLayout*)collectionViewLayout
 sizeForItemAtIndexPath:(NSIndexPath *)indexPath;

This was the initial method I looked at to size the cells. However, this is called BEFORE the cells are created out of the storyboard.

Is there a way to layout the collection and size the cells AFTER they have been rendered, and I know the actual size of the cell?

Pang
  • 9,564
  • 146
  • 81
  • 122
Shocks
  • 808
  • 1
  • 9
  • 18

4 Answers4

76

I think your are looking for the invalidateLayout method you can call on the .collectionViewLayout property of your UICollectionView. This method regenerates your layout, which in your case means also calling -collectionView: layout: sizeForItemAtIndexPath:, which is the right place to reflect your desired item size. Jirune points the right direction on how to calculate them.

An example for the usage of invalidateLayout can be found here. Also consult the UICollectionViewLayout documentation on that method:

Invalidates the current layout and triggers a layout update.

Discussion:

You can call this method at any time to update the layout information. This method invalidates the layout of the collection view itself and returns right away. Thus, you can call this method multiple times from the same block of code without triggering multiple layout updates. The actual layout update occurs during the next view layout update cycle.

Edit:

For storyboard collection view which contains auto layout constraints, you need to override viewDidLayoutSubviews method of UIViewController and call invalidateLayout collection view layout in this method.

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    [yourCollectionView.collectionViewLayout invalidateLayout];
}
Community
  • 1
  • 1
Jan Z.
  • 6,883
  • 4
  • 23
  • 27
  • I am following your implementation approach, there is one problem I met: after calling `invalidateLayout` sometimes cells disappear / get invisible, so I can not get them in `sizeForItemAtIndexPath` with `cellForItemAtIndexPath`. Btw I use cells to get their size, I do not have a separate store for it. – János Aug 14 '14 at 13:27
  • Awesome! Had stuck on cell width which was not change as it is assign by auto layout, but after reading this solution I did figure that out. P.S. I put code for auto layout issue, hope you dont mind. – Kampai Oct 08 '15 at 07:53
  • 1
    Thanks a lot ! I didn't know that this would solve the issue ! I was calling reloadData and it was a costly operation and it didn't give me expected results though. – Rizwan Ahmed Sep 17 '17 at 07:59
  • 2
    Calling `[collectionViewLayout invalidateLayout]` in `viewDidLayoutSubviews` gave me an infinite loop. – Daniel Oct 23 '19 at 12:07
  • with collectionView.reloadData(), I was getting infinite loop in some cases but collectionView.collectionViewLayout.invalidateLayout() solved my problem. Thanks – Rajath Kornaya May 13 '22 at 07:29
5

subclass UICollectionViewCell and override layoutSubviews like this

hereby you will anchor cell leading and trailing edge to collectionView

override func layoutSubviews() {
        super.layoutSubviews()

        self.frame = CGRectMake(0, self.frame.origin.y, self.superview!.frame.size.width, self.frame.size.height)
    }
János
  • 32,867
  • 38
  • 193
  • 353
4

Hey in the above delegate method itself, you can calculate the UILabel size using the below tricky way and return the UICollectionViewCell size based on that calculation.

    // Calculate the expected size based on the font and 
    // linebreak mode of your label
    CGSize maximumLabelSize = CGSizeMake(9999,9999);

    CGSize expectedLabelSize = 
     [[self.dataSource objectAtIndex:indexPath.item] 
             sizeWithFont:[UIFont fontWithName:@"Arial" size:18.0f] 
        constrainedToSize:maximumLabelSize 
            lineBreakMode:NSLineBreakByWordWrapping];
Alex Cio
  • 6,014
  • 5
  • 44
  • 74
Jirune
  • 2,310
  • 3
  • 21
  • 19
2
- (void)viewDidLoad {
    self.collectionView.prefetchingEnabled = NO;
}

In iOS 10, prefetchingEnabled is YES by default. When YES, the collection view requests cells in advance of when they will be displayed. It leads to crash in iOS 10

FelixSFD
  • 6,052
  • 10
  • 43
  • 117
Sakshi Jain
  • 125
  • 1
  • 7
  • 1
    Whilst this code snippet is welcome, and may provide some help, it would be [greatly improved if it included an explanation](//meta.stackexchange.com/q/114762) of *how* and *why* this solves the problem. Remember that you are answering the question for readers in the future, not just the person asking now! Please [edit] your answer to add explanation, and give an indication of what limitations and assumptions apply. – Toby Speight Mar 10 '17 at 11:18
  • @TobySpeight In iOS10, prefetchingEnabled is YES by default. When YES, the collection view requests cells in advance of when they will be displayed. It leads to crash in iOS10 – Sakshi Jain Mar 14 '17 at 06:38
  • That looks like a good explanation - please **[edit]** your answer to incorporate it. Thanks! – Toby Speight Mar 14 '17 at 08:43