How to adjust height of UICollectionView to be the height of the content size of the UICollectionView?

127,669

Solution 1

I would suggest the following:

  1. Add a height constraint to your collection view.
  2. Set its priority to 999.
  3. Set its constant to any value that makes it reasonably visible on the storyboard.
  4. Change the bottom equal constraint of the collection view to greater or equal.
  5. Connect the height constraint to an outlet.
  6. Every time you reload the data on the collection view do the following:

You may also want to consider the Inset of the collection view by adding it to the content size.

Code Sample:

CGFloat height = myCollectionView.collectionViewLayout.collectionViewContentSize.height
heightConstraint.constant = height
self.view.setNeedsLayout() Or self.view.layoutIfNeeded()

Explanation: Extra, You don't have to read if you understand it. obviously!!

The UI will try to reflect all the constraints no matter what are their priorities. Since there is a height constraint with lower priority of (999), and a bottom constraint of type greater or equal. whenever, the height constraint constant is set to a value less than the parent view height the collection view will be equal to the given height, achieving both constraints.

But, when the height constraint constant set to a value more than the parent view height both constraints can't be achieved. Therefore, only the constraint with the higher priority will be achieved which is the greater or equal bottom constraint.

The following is just a guess from an experience. So, it achieves one constrant. But, it also tries to make the error in the resulted UI for the other un-achieved lower priority constraint as lowest as possible. Therefore, the collection view height will be equal to the parent view size.

Solution 2

In Swift 5 and Xcode 10.2.1

My CollectionView name is myCollectionView

  1. Fix height for your CollectionView

  2. Create Outlet for your CollectionViewHeight

    IBOutlet weak var myCollectionViewHeight: NSLayoutConstraint!
    
  3. Use below code

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        let height = myCollectionView.collectionViewLayout.collectionViewContentSize.height
        myCollectionViewHeight.constant = height
        self.view.layoutIfNeeded()
    }
    

Dynamic width for cell based on text content...

Dynamic cell width of UICollectionView depending on label width

Solution 3

1) Set Fix Height of your CollectionView.

2) Create Outlet of this CollectionView Height Constant. Like :

IBOutlet NSLayoutConstraint *constHeight;

3) Add below method in your .m file:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    CGFloat height = collectionMenu.collectionViewLayout.collectionViewContentSize.height;
    constHeight.constant = height;
}

Solution 4

I ended up, by subclassing the UICollectionView and overriding some methods as follows.

  • Returning self.collectionViewLayout.collectionViewContentSize for intrinsicContentSize makes sure, to always have the correct size
  • Then just call it whenever it might change (like on reloadData)

Code:

override func reloadData() {
    super.reloadData()
    self.invalidateIntrinsicContentSize()
}

override var intrinsicContentSize: CGSize {
    return self.collectionViewLayout.collectionViewContentSize
}

But be aware, that you lose "cell re-using", if you display large sets of data, eventhough they don't fit on the screen.

Solution 5

This seemed like the simplest solution for me.

class SelfSizingCollectionView: UICollectionView {
    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    private func commonInit() {
        isScrollEnabled = false
    }

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

    override func reloadData() {
        super.reloadData()
        self.invalidateIntrinsicContentSize()
    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }
}

You may not need to override reloadData

Share:
127,669

Related videos on Youtube

charbinary
Author by

charbinary

Updated on July 08, 2022

Comments

  • charbinary
    charbinary almost 2 years

    I would like the UICollectionView (The red one) to shrink to the height of the content size in this case UICollectionViewCells(the yellow ones) because there is a lot of empty space. What I tried is to use:

    override func layoutSubviews() {
        super.layoutSubviews()
        if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
            self.invalidateIntrinsicContentSize()
        }
    }
    
    override var intrinsicContentSize: CGSize {
        return self.collection.contentSize
    }
    

    but return self.collection.contentSize always return (width, 0) and for this reason it shrinks too much to value of height 30 (The value which I set in the XIB file for the height, although I have constaint >= 30).

    enter image description here

    • dahiya_boy
      dahiya_boy about 7 years
      You've to take height constraint outlet of ColletionView and adjust height like this heightConstraint.constant = collectionView.contentSize.height
    • Georgi Boyadzhiev
      Georgi Boyadzhiev about 7 years
      Are You using custom layout or the provided by Apple - UICollectionViewFlowLayout?
    • charbinary
      charbinary about 7 years
      I am using the default one UICollectionViewFlowLayout
    • Georgi Boyadzhiev
      Georgi Boyadzhiev about 7 years
      Than You should use collectionViewLayout.collectionViewContentSize, see @hasan83's answer. It is a possibility for You.
  • Georgi Boyadzhiev
    Georgi Boyadzhiev about 7 years
    This method returns the size for a specific cell at index path, if You set the height to noOfCell*heightOfCell this will set the height of all the cells.
  • charbinary
    charbinary about 7 years
    @Georgi Boyadzhiev, that's true.
  • nr5
    nr5 over 6 years
    Mine expands to some extent but not perfect. It misses about 3 4 lines of the label. Any idea? Here is how it looks: ibb.co/jeO9Rv ibb.co/iV8b6v
  • hasan
    hasan over 6 years
    @Nil Do you have a header or footer on your collection view? the height of the footer and the header must be added.
  • nr5
    nr5 over 6 years
    Nevermind, it worked. The height constraint was set to proportional height that's why setting just only the constant was making some difference not much.
  • nr5
    nr5 over 6 years
    While we are talking, do you have any idea how to pin a cell to the top? In my horizontal collectionview some cells are not pinned to the top. Just randomly floating in middle. Here: ibb.co/fHbSwv
  • Mitesh Dobareeya
    Mitesh Dobareeya over 6 years
    @hasan83 I am getting some extra height of collection view by this way, any suggestion ?
  • hasan
    hasan over 6 years
    @MiteshDobareeya hmm, maybe its something with the collection view headers and footers? did you add any view in the collection view on the storyboard?
  • hasan
    hasan over 6 years
    or maybe its with point 4. Change the bottom equal constraint of the collection view to greater or equal. Greater or equal which means the height of the collectionview should always be less or equal to the height of its parent view
  • Mitesh Dobareeya
    Mitesh Dobareeya over 6 years
    @hasan83 No i have not added any view in collection view or no any header and footers are there. In my design i have just scroll view and collection view is inside content view of scrollview and 2 other views. All have fix height and collection view has fix height outlet. So when i reloading collection view, i am just getting hight based on it's content and assigning to its height constant. But unfortunately getting some extra height.
  • hasan
    hasan over 6 years
    what I proposed in my answer doesn't work as it is on a scrollview. thats the problem. you got the idea on how you can do that on a view. so, think the same to figure out a solution for your situation
  • Carl
    Carl about 5 years
    Like others in the comments I was getting some extra height when using this method. To fix this I simply did a .reloadData() and then changed the constant of the height constraint within a collection view performBatchUpdates.
  • Pankaj Kulkarni
    Pankaj Kulkarni almost 5 years
    This worked using Swift 5 and Xcode 10.2.1. Thanks.
  • cabyambo
    cabyambo almost 5 years
    This works to make the UICollectionView initially the size of its content, but if the content changes ie: insert or delete rows, it doesn't resize. And if I call reload data the collection view stutters as it resizes. Any way to stop this stuttering?
  • d4Rk
    d4Rk almost 5 years
    Did you try calling self.invalidateIntrinsicContentSize() without calling reloadData()? Maybe this could work.
  • n13
    n13 almost 5 years
    By far the most beautiful solution - at least for my use case. Thank you. It solves the problem of timing, layout needs all other constraints and views to be sized to determine content size - this one just works.
  • n13
    n13 almost 5 years
    Actually - I had to constrain the intrinsic size to be at least 1 - never 0 - for this to correctly work. Otherwise there were small errors in the calculation, cutting off the bottom of the view by a few pixels.
  • n13
    n13 almost 5 years
    Tried, but that didn't work for me. The other subclassing solution by d4Rk worked with minor modifications
  • ArielSD
    ArielSD over 4 years
    Are you able to use this programmatically as well, or just with storyboards?
  • hasan
    hasan over 4 years
    You can add the constraints programmatically. if that what you mean @ArielSD we already have some Code involved even with the storyboard version!
  • ArielSD
    ArielSD over 4 years
    @hasan The collectionView I'm using is nested in a stackView in a tableViewCell - where should I call setNeedsLayout?
  • AnthonyMDev
    AnthonyMDev over 4 years
    Why does this mean you lose cell re-use? Not clear on what is causing that using this method.
  • d4Rk
    d4Rk over 4 years
    @AnthonyMDev Because using this approach, you're resizing the collection view to fit all items at the same time, so all items are loading any time.
  • AnthonyMDev
    AnthonyMDev over 4 years
    Ahhhh that makes sense. I was thinking it was breaking the actual dequeue logic, but it's just a fact of the collection view displaying all the cells at all times. Thanks for the response!
  • Karthi Rasu
    Karthi Rasu about 4 years
    what does it myCollectionView in your code.Where you declared
  • Naresh
    Naresh about 4 years
    @ Karthi Rasu, myCollectionView is your collection view height constraint. If you are using storyboard fix height of collectionView.
  • Teddy K
    Teddy K over 3 years
    This was the only thing that worked for me on iOS 14. Thanks!
  • Vinayak Bhor
    Vinayak Bhor over 3 years
    Perfect solution !!
  • Pranav Pravakar
    Pranav Pravakar over 3 years
    @cabyambo Were you able to find the solution for it?
  • QuangDT
    QuangDT over 3 years
    If you find the contentsize returned is higher than actual, set the initial height constant to something large like 1000.
  • Andy Weinstein
    Andy Weinstein almost 3 years
    I use RxSwift to detect when the height of the collection view changes (like when I configure it by calling reloadData after updating the relevant data). In the closure that is invoked by the observer, I do the update of the height constraint and call layoutIfNeeded - also on the superviews. The code for the observer is: collectionView.rx.observe(CGSize.self , "contentSize").subscribe(onNext: { (size) in <your code here>}
  • Naresh
    Naresh over 2 years
    Dynamic width for cell... stackoverflow.com/questions/23134986/…
  • iosMentalist
    iosMentalist over 2 years
    can u share the full code ?
  • Wimukthi Rajapaksha
    Wimukthi Rajapaksha over 2 years
    if you're using UIScrollView as parent of the collection view, need to do slight change. Try, stackoverflow.com/a/70285535/10505343
  • cristian_064
    cristian_064 almost 2 years
    that is very weird I don't know why need to setup first value 1 to start calculate the final value.