How to determine height of UICollectionView with FlowLayout
IosHeightUicollectionviewAutolayoutIos Problem Overview
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.
Ios Solutions
Solution 1 - Ios
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.
Solution 2 - Ios
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
Solution 3 - Ios
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];
Solution 4 - Ios
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
}
}
Solution 5 - Ios
user1046037 answer in Swift...
class DynamicCollectionView: UICollectionView {
override func layoutSubviews() {
super.layoutSubviews()
if bounds.size != intrinsicContentSize() {
invalidateIntrinsicContentSize()
}
}
override func intrinsicContentSize() -> CGSize {
return self.contentSize
}
}
Solution 6 - Ios
Swift 4.2
class DynamicCollectionView: UICollectionView {
override func layoutSubviews() {
super.layoutSubviews()
if bounds.size != intrinsicContentSize {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
return self.contentSize
}
}
Solution 7 - Ios
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
}
}
Solution 8 - Ios
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.
Solution 9 - Ios
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)
Solution 10 - Ios
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
}
}
Solution 11 - Ios
In addition,you'd better calling reloadData
before getting the height:
theCollectionView.collectionViewLayout.collectionViewContentSize;
Solution 12 - Ios
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
After
Solution 13 - Ios
assuming your collectionView is named as collectionView you can take the height as follows.
let height = collectionView.collectionViewLayout.collectionViewContentSize.height