How to determine height of UICollectionView with FlowLayout
Asked Answered
C

13

138

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.

Cone answered 9/12, 2012 at 14:54 Comment(0)
C
278

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.

Cone answered 9/12, 2012 at 15:9 Comment(11)
whoa, I wish UITableView has something similarEncroachment
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!Slacken
What's the difference between collectionViewContentSize and contentSize of the scroll view?Whichsoever
When you call contentSize of the collection view in viewDidLoad, it returns the frame of the collection view, collectionViewContentSize returns correct content size.Genealogy
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?Unorganized
For me calling reloadData before querying the collectionViewContentSize size helped.Dynamiter
@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. :(Drugi
@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.Revetment
@ChrisChen TableView actually does have something similar, and even more easier. Just call myTableView.contentSize and you'll get the content size as CGSize!Kassel
For whom it can help: I had to do a "layoutIfNeeded()" before this so I could get it working.Whiplash
When is collectionViewContentSize set? When can we read from it?Accusation
B
43

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
Bachelorism answered 7/10, 2014 at 8:53 Comment(8)
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 :(Nyctaginaceous
Awesome. Short and sweet solution to my UICollectionView inside of a UIScrollView!Hotchpotch
@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?Blatman
One important thing is to disable scrolling and scrollbars in the collection viewNyctaginaceous
Same problem as Georg, it's about 50px in height when it should be more than 400Glosseme
@Nyctaginaceous i have exactly the same problem and haven't found a solution yet. Did you find one?Ormuz
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!Nyctaginaceous
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.Bachelorism
C
26

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];
Carcass answered 7/2, 2015 at 4:40 Comment(0)
A
14

Swift 4.2

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

    override var intrinsicContentSize: CGSize {
        return self.contentSize
    }
}
Armanda answered 9/1, 2019 at 17:43 Comment(0)
L
13

user1046037 answer in Swift...

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

    override func intrinsicContentSize() -> CGSize {
        return self.contentSize
    }
}
Lignin answered 20/4, 2016 at 15:9 Comment(1)
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 ?Ambulate
F
13

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
    }

}
Fourth answered 6/11, 2016 at 6:33 Comment(2)
Im a fresher, and Im not sure about where and how to use this in my ViewController, Can you please make an edit with the answer.....?Exospore
just set this class to your collectionview in StoryboardFourth
S
9

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
  }
}
Social answered 23/3, 2018 at 3:1 Comment(1)
I found that its not working in iPhone 6/7 plus and XR,XS-MAXLevee
D
6

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.

Defrost answered 19/4, 2017 at 5:38 Comment(4)
@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.Defrost
am getting this error : An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.Thickness
@ShikhaSharma: Sorry, It's my mistake to specify the method. Try to put removeobserver code in viewWillDisappear.Defrost
Just made use of this and it worked pretty well - it had the advantage of informing my callback before the screen actually displayed to the user, so that I was able to adjust a critical constraint size based on the final size of the UICollectionView being rendered.Alexipharmic
I
2

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

theCollectionView.collectionViewLayout.collectionViewContentSize;
Isma answered 29/10, 2018 at 3:25 Comment(0)
B
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)
Bollay answered 21/10, 2019 at 8:24 Comment(0)
R
1

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
    }
}
Rhombohedral answered 1/3, 2018 at 17:28 Comment(0)
S
1

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

let height = collectionView.collectionViewLayout.collectionViewContentSize.height
Studer answered 11/12, 2019 at 16:44 Comment(0)
G
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

Gamali answered 6/5, 2019 at 17:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.