UICollectionView autosize height

IosObjective CUicollectionviewNslayoutconstraint

Ios Problem Overview


How do I properly resize a UICollectionView so that it fully displays its contents? I have tried many things, including setting its frame, calling reloadData and invalidating the layout:

self.collectionView.contentSize = CGSizeMake(300, 2000);
self.collectionView.frame = CGRectMake(0, 0, 300, 2000);
[self.collectionView reloadData];
[self.collectionView.collectionViewLayout invalidateLayout];

but none of this has any effect. After pressing the button I still see the initial view, like this:

collection view only showing part of the content after frame resize

I have a small demo program where I have a data source producing 100 elements. In Interface Builder I initially set the size of the UICollectionView to a small value so that not all elements fit, after that I press a button after which the code above is executed. I expect the UICollectionView to now show all elements, but it doesn't.

EDIT: The demo program can be found at https://github.com/mjdemilliano/TestUICollectionView.

EDIT2: I have observed that the frame update is lost at some point, because if I press the button again, the current frame is back to the old value. After adding some log statements in the button event handler, the log output is:

before: frame = {{0, 58}, {320, 331}}, contentSize = {320, 1190}
update button pressed
after: frame = {{0, 0}, {300, 2000}}, contentSize = {300, 2000}
before: frame = {{0, 58}, {320, 331}}, contentSize = {320, 1190}
update button pressed
after: frame = {{0, 0}, {300, 2000}}, contentSize = {300, 2000}

I don't understand why the frame change is not kept, what is changing it.

At some point I will replace the hardcoded values by values obtained from the flow layout, but I wanted to rule that out and keep my example as simple as possible.

Context: What I want to do eventually is the following: I have a scrollable view with various controls like labels and images, and a collection view with dynamic content. I want to scroll all that, not just the collection view, therefore I am not using the collection view's own scrolling facilities, which work fine.

Ios Solutions


Solution 1 - Ios

I solved this eventually by fixing all Auto Layout issues, fixing the height of the collection view using a constraint. Then, whenever I know the content has changed I update the value of the constraint using the value collectionView.contentSize.height:

self.verticalLayoutConstraint.constant = self.collectionView.contentSize.height;

Then the collection view is resized properly and it behaves nicely within the overall scrollview. I have updated the GitHub test project with my changes.

To me, doing this by updating the constraint manually instead of being able to tell iOS: "make the frame height of the collection view as large as needed" does not feel right to me, but it's the best I have come up with so far. Please post a better answer if you have one.

Solution 2 - Ios

It seems to work nicely with a custom UICollectionView class.

class AutoSizedCollectionView: UICollectionView {

    override var contentSize: CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        layoutIfNeeded()
        return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
    }
}

Set your custom class in the interface builder:

enter image description here

This way you can also set your collection views intrinsic size to 'placeholder' in interface builder to avoid having to set a height constraint.

enter image description here

I hope this helps someone else.

Solution 3 - Ios

Here's my implementation in Swift 3:

override func sizeThatFits(_ size: CGSize) -> CGSize {
    if (self.superview != nil) {
        self.superview?.layoutIfNeeded()
    }

    return collectionView.contentSize
}

Solution 4 - Ios

UICollectionViewFlowLayout *flowLayout;
flowLayout = [[UICollectionViewFlowLayout alloc]init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
[flowLayout setMinimumInteritemSpacing:0.0f];
[flowLayout setMinimumLineSpacing:0.0f];
[self.collectionView setPagingEnabled:NO];
[flowLayout setItemSize:CGSizeMake(322.0, 148.0)];  //important to leave no white space between the images
[self.collectionView setCollectionViewLayout:flowLayout];

I found that autolayout in the storyboard is not helping too much. A correct setting for the UICollectionViewFlowLayout for your collectionView is the real help. If you adjust item size with setItemSize, you may get the result you want.

Solution 5 - Ios

The simplest method I found is to override sizeThatFits: methods as is:

- (CGSize)sizeThatFits:(CGSize)size
{
    if( self.superview )
        [self.superview layoutIfNeeded]; // to force evaluate the real layout
    
    return self.collectionViewLayout.collectionViewContentSize;
}

Solution 6 - Ios

Here's a way to bind the CollectionView's height via it's intrinsic size. I used it to properly size a CollectionView inside a TableView Cell (with dynamic cells height). and it works perfectly.

First, add this to your UICollectionView subclass:

override var intrinsicContentSize: CGSize {
    get {
        return self.contentSize
    }
}

Then call layoutIfNeeded() after you reload data:

reloadData()
layoutIfNeeded()

Solution 7 - Ios

You can try out my custom AGCollectionView class

  • Assign a height constraint of collectionView using a storyboard or programmatically.

  • Assign this class to your UICollectionView.

class AGCollectionView: UICollectionView {
    fileprivate var heightConstraint: NSLayoutConstraint!
    
    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
        self.associateConstraints()
    }
    
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.associateConstraints()
    }
    
    override open func layoutSubviews() {
        super.layoutSubviews()
        
        if self.heightConstraint != nil {
            self.heightConstraint.constant = floor(self.contentSize.height)
        }
        else{
            self.sizeToFit()
            print("Set a heightConstraint set size to fit content")
        }
    }
    
    func associateConstraints() {
        // iterate through height constraints and identify
        
        for constraint: NSLayoutConstraint in constraints {
            if constraint.firstAttribute == .height {
                if constraint.relation == .equal {
                    heightConstraint = constraint
                }
            }
        }
    }
}

Solution 8 - Ios

  1. Add IBOutlet for CollectionView Height Constraint --> Like @IBOutlet weak var collectionViewHeight: NSLayoutConstraint!
  2. Add Below snipped code.

enter image description here

Solution 9 - Ios

For me it is even simpler I think

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

{
//add following code line after adding cells, before Return
...........
.........
scrollView.contentSize = = collectionView.contentSize;

//now scrollView size is equal to collectionView size. No matter how small or big it is.

return cell;
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMartijn de MillianoView Question on Stackoverflow
Solution 1 - IosMartijn de MillianoView Answer on Stackoverflow
Solution 2 - IosTrevorView Answer on Stackoverflow
Solution 3 - IosCraig HollidayView Answer on Stackoverflow
Solution 4 - Iosflame3View Answer on Stackoverflow
Solution 5 - IosNicolas BuquetView Answer on Stackoverflow
Solution 6 - IosAmitPView Answer on Stackoverflow
Solution 7 - IosAshvinGudaliyaView Answer on Stackoverflow
Solution 8 - IosthevikasnayakView Answer on Stackoverflow
Solution 9 - Iosuser2511630View Answer on Stackoverflow