124

I've got an UICollectionView with an UICollectionViewFlowLayout, and i want to calculate its content size (for return in intrinsicContentSize needed for adjusting its height via AutoLayout).

The problems is: Even if I have a fixed and equal height for all cells, I don't know how many "rows"/lines I have in the UICollectionView. I also can't determine that count by the number of items in my data source, since the cells representing the data items vary in width, so does consequently the number of items I have in one line of the UICollectionView.

Since I couldn't find any hints on this topic in the official documentation and googling didn't bring me any further, any help and ideas would be appreciated very much.

Ignatius Tremor
  • 6,476
  • 4
  • 20
  • 25

13 Answers13

259

Whoa! For some reason, after hours of research, I now found a pretty easy answer to my question: I was completely searching in the wrong place, digging through all the documentation I could find on UICollectionView.

The simple and easy solution lies in the underlying layout: Just call collectionViewContentSize on your myCollectionView.collectionViewLayout property and you get the height and width of the content as CGSize. It's as easy as that.

Ignatius Tremor
  • 6,476
  • 4
  • 20
  • 25
  • 1
    whoa, I wish UITableView has something similar – Chris Chen Feb 26 '14 at 00:27
  • 1
    Ignatus, @jbandi, Chris et al, can you expand on this? When I tested with a horizontal scroll direction with a large inter-item spacing (so that the items would lay out horizontally), the collection view returned an invalid intrinsic content size (-1, -1), and collectionViewContentSize returned the what was effectively the bounds of the collection view - not taking into account that there was only one row surrounded by space. In short, it was not very intrinsic. Thanks! – Chris Conover May 15 '14 at 00:28
  • 8
    What's the difference between collectionViewContentSize and contentSize of the scroll view? – rounak Jan 06 '15 at 16:59
  • When you call contentSize of the collection view in viewDidLoad, it returns the frame of the collection view, collectionViewContentSize returns correct content size. – Fury Mar 10 '16 at 12:40
  • collectionViewContentSize only works on the 2nd (sometimes also 3rd) time for me, the first time it always returns a too large width (I have pinned the width, but care for a variable height), thus I have to force relayout at least once. Is there a better way? – DrMickeyLauer Apr 06 '17 at 13:57
  • For me calling `reloadData` before querying the `collectionViewContentSize` size helped. – Sebastian Jun 15 '17 at 11:39
  • @MananDevani, could you please share your code or tutorial relating to this problem?. I have got such a problem right now. I don't know how to solve it. :( – May Phyu Sep 05 '17 at 04:30
  • @May Phyu, Create outlet connection for UICollectionViewHeight and use following method. - (void)setCollectionViewHeight { self.collectionViewHeight.constant = self.collectionView.collectionViewLayout.collectionViewContentSize.height; } // Call this method after loading the data. – Manan Devani Sep 07 '17 at 08:01
  • @ChrisChen TableView actually does have something similar, and even more easier. Just call `myTableView.contentSize` and you'll get the content size as `CGSize`! – Chen Li Yong Jul 02 '18 at 07:55
  • This saved me, Thanks! – Ajay Kumar Dec 23 '18 at 21:46
  • 1
    For whom it can help: I had to do a "layoutIfNeeded()" before this so I could get it working. – asanli Apr 05 '19 at 16:58
  • When is `collectionViewContentSize` set? When can we read from it? – ArielSD Aug 15 '19 at 15:56
38

In case you are using Auto layout, then you could create a subclass of UICollectionView

If you use the below the code then you don't have to specify any height constraints for the collection view as it would vary based on the contents of the collection view.

Given below is the implementation:

@interface DynamicCollectionView : UICollectionView

@end

@implementation DynamicCollectionView

- (void) layoutSubviews
{
    [super layoutSubviews];

    if (!CGSizeEqualToSize(self.bounds.size, [self intrinsicContentSize]))
    {
        [self invalidateIntrinsicContentSize];
    }
}

- (CGSize)intrinsicContentSize
{
    CGSize intrinsicContentSize = self.contentSize;

    return intrinsicContentSize;
}

@end
user1046037
  • 14,608
  • 12
  • 79
  • 117
  • 13
    didn't work for me... I am using my CollectionView inside a UITableViewCell and would like the tableview to grow in height as the collection view grows in height using iOS8 UITableViewAutomaticDimension But my collectionview stays small :( – Georg Nov 11 '14 at 13:28
  • Awesome. Short and sweet solution to my UICollectionView inside of a UIScrollView! – dotToString May 30 '15 at 00:50
  • @Georg, I am trying to do something similar. But when there is a deadlock. The collection view will get its layout information after cell loads on View and the cell loads after the height is calculated but the height need to be calculated from the collectionview. How did you fix this? – thesummersign Jun 18 '15 at 07:17
  • 2
    One important thing is to disable scrolling and scrollbars in the collection view – Georg Jun 18 '15 at 13:07
  • 1
    Same problem as Georg, it's about 50px in height when it should be more than 400 – xtrinch Jun 08 '16 at 13:40
  • @Georg i have exactly the same problem and haven't found a solution yet. Did you find one? – Maverick1st Feb 24 '17 at 13:19
  • 3
    Yes, I set the collectionview to "not scrollabe, no scollbars" and then reload it then the content of the tableview is set. Also the collectionview is a subclass that returns it's content size as intrinsicContentSize. Hope that helps! – Georg Feb 24 '17 at 18:55
  • IMHO it might be better to use the collection view without using the table view. If the flow layout doesn't meet your requirement you could create your own custom layout. – user1046037 Feb 25 '17 at 00:52
25

At viewDidAppear you can get it by:

float height = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;

Maybe when you reload data then need to calculate a new height with new data then you can get it by: add observer to listen when your CollectionView finished reload data at viewdidload:

[self.myCollectionView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld context:NULL];

Then add bellow function to get new height or do anything after collectionview finished reload:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary  *)change context:(void *)context
{
    //Whatever you do here when the reloadData finished
    float newHeight = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;    
}

And don't forget to remove observer:

[self.myCollectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];
lee
  • 7,016
  • 8
  • 41
  • 56
12

Swift 3 code for user1046037 answer

import UIKit

class DynamicCollectionView: UICollectionView {

    override func layoutSubviews() {
        super.layoutSubviews()
        if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
            self.invalidateIntrinsicContentSize()
        }

    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

}
Mohammad Sadiq Shaikh
  • 2,886
  • 8
  • 32
  • 53
11

user1046037 answer in Swift...

class DynamicCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if bounds.size != intrinsicContentSize() {
            invalidateIntrinsicContentSize()
        }
    }

    override func intrinsicContentSize() -> CGSize {
        return self.contentSize
    }
}
ManoDestra
  • 5,756
  • 6
  • 22
  • 48
Nick Wood
  • 660
  • 6
  • 8
  • It's work perfectly but If you have something like `UILabel` above, the content from `collectionView` pass over other element above, do you have another solution ? – KolaCaine Feb 18 '19 at 18:58
8

Swift 4.2

class DynamicCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if bounds.size != intrinsicContentSize {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        return self.contentSize
    }
}
Cesare
  • 8,326
  • 14
  • 64
  • 116
7

Swift 4

class DynamicCollectionView: UICollectionView {
  override func layoutSubviews() {
    super.layoutSubviews()
    if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
      self.invalidateIntrinsicContentSize()
    }
  }

  override var intrinsicContentSize: CGSize {
    return collectionViewLayout.collectionViewContentSize
  }
}
Wilson
  • 8,463
  • 3
  • 36
  • 42
4

It's too late to answer this question but I have recently gone through this issue.
@lee's above answer help me a lot to get my answer. But that answer is limited to objective-c and I was working in swift.
@lee With reference to your answer, allow me to let the swift user get this issue solved easily. For that, please follow below steps:

Declare a CGFloat variable in declaration section:

var height : CGFloat!

At viewDidAppear you can get it by:

height = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height

Maybe when you reload data then need to calculate a new height with new data then you can get it by: addObserver to listen when your CollectionView finished reload data at viewWillAppear:

override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(true)
        ....
        ....
        self.shapeCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.Old, context: nil)
    }

Then add bellow function to get new height or do anything after collectionview finished reload:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        let newHeight : CGFloat = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height

        var frame : CGRect! = self.myCollectionView.frame
        frame.size.height = newHeight

        self.myCollectionView.frame = frame
    }

And don't forget to remove observer:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)
    ....
    ....
    self.myCollectionView.removeObserver(self, forKeyPath: "contentSize")
}

I hope this will help you to solve your issue in swift.

Er. Vihar
  • 1,335
  • 1
  • 11
  • 27
  • @ShikhaSharma: You can add the removeObserver code within "ViewDidDisappear" method, which will remove observer only after the view confirms that its disappeared. Please check the updated answer. – Er. Vihar Nov 06 '17 at 12:47
  • am getting this error : An -observeValueForKeyPath:ofObject:change:context: message was received but not handled. – Shikha Sharma Nov 06 '17 at 12:52
  • @ShikhaSharma: Sorry, It's my mistake to specify the method. Try to put removeobserver code in viewWillDisappear. – Er. Vihar Nov 06 '17 at 13:15
2

This answer is based on @Er. Vihar's answer. You can easily implement the solution by using RxSwift

     collectionView.rx.observe(CGSize.self , "contentSize").subscribe(onNext: { (size) in
        print("sizer \(size)")
    }).disposed(by: rx.disposeBag)
0

Swift version of @user1046037:

public class DynamicCollectionView: UICollectionView {

    override public func layoutSubviews() {
        super.layoutSubviews()
        if !bounds.size.equalTo(intrinsicContentSize) {
            invalidateIntrinsicContentSize()
        }
    }

    override public var intrinsicContentSize: CGSize {
        let intrinsicContentSize: CGSize = contentSize
        return intrinsicContentSize
    }
}
Joshua Hart
  • 766
  • 1
  • 17
  • 28
0

In addition,you'd better calling reloadData before getting the height:

theCollectionView.collectionViewLayout.collectionViewContentSize;
guozqzzu
  • 579
  • 7
  • 12
0

Swift 4.2, Xcode 10.1, iOS 12.1:

For some reason, collectionView.contentSize.height was appearing smaller than the resolved height of my collection view. First, I was using an auto-layout constraint relative to 1/2 of the superview's height. To fix this, I changed the constraint to be relative to the "safe area" of the view.

This allowed me to set the cell height to fill my collection view using collectionView.contentSize.height:

private func setCellSize() {
    let height: CGFloat = (collectionView.contentSize.height) / CGFloat(numberOfRows)
    let width: CGFloat = view.frame.width - CGFloat(horizontalCellMargin * 2)

    let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
    layout.itemSize = CGSize(width: width, height: height)
}

Before

height constraint relative to superview bad simulator result

After

height constraint relative to safe area good simulator result

Andrew Kirna
  • 986
  • 11
  • 16
0

assuming your collectionView is named as collectionView you can take the height as follows.

let height = collectionView.collectionViewLayout.collectionViewContentSize.height