115

In iOS 8 the UICollectionViewFlowLayout supports automatically resizing cells based on their own content size. This resizes the cells in both width and height according to their content.

Is it possible to specify a fixed value for the width (or height) of all the cells and allow the other dimensions to resize?

For a simple example consider a multi-line label in a cell with constraints positioning it to the sides of the cell. The multi-line label could be resized different ways to accommodate the text. The cell should fill the width of the collection view and adjust it's height accordingly. Instead, the cells are sized haphazardly and it even causes a crash when the cell size is larger than the non-scrollable dimension of the collection view.


iOS 8 introduces the method systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority: For each cell in the collection view the layout calls this method on the cell, passing in the estimated size. What would make sense to me would be to override this method on the cell, pass in the size that is given and set the horizontal constraint to required and a low priority to the vertical constraint. This way the horizontal size is fixed to the value set in the layout and the vertical size can be flexible.

Something like this:

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    UICollectionViewLayoutAttributes *attributes = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
    attributes.size = [self systemLayoutSizeFittingSize:layoutAttributes.size withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    return attributes;
}

The sizes given back by this method, however, are completely strange. The documentation on this method is very unclear to me and mentions using the constants UILayoutFittingCompressedSize UILayoutFittingExpandedSize which just represent a zero size and a pretty large one.

Is the size parameter of this method really just a way to pass in two constants? Is there no way to achieve the behavior I expect of getting the appropriate height for a given size?


Alternate Solutions

1) Adding constraints that will be specify a specific width for the cell achieves the correct layout. This is a poor solution because that constraint should be set to the size of the cell's collection view which it has no safe reference to. The value for that constraint could be passed in when the cell is configured, but that also seems completely counterintuitive. This is also awkward because adding constraints directly to a cell or it's content view is causing many problems.

2) Use a table view. Table views work this way out of the box as cells have a fixed width, but this would not accommodate other situations like an iPad layout with fixed width cells in multiple columns.

Anthony Mattox
  • 6,809
  • 5
  • 40
  • 56

5 Answers5

137

It sounds like what you are asking for is a way to use UICollectionView to produce a layout like UITableView. If that's really what you want, the right way to do this is with a custom UICollectionViewLayout subclass (maybe something like SBTableLayout).

On the other hand, if you're really asking if there is a clean way to do this with the default UICollectionViewFlowLayout, then I believe there is no way. Even with iOS8's self-sizing cells, it is not straightforward. The fundamental problem, as you say, is that the flow layout's machinery provides no way to fix one dimension and let another respond. (In addition, even if you could, there would be additional complexity around needing two layout passes to size the multi-line labels. This might not fit with how self-sizing cells want to compute all sizing via one call to systemLayoutSizeFittingSize.)

However, if you still want to create a tableview-like layout with a flow layout, with cells that determine their own size, and respond naturally to the collection view's width, of course it is possible. There is still the messy way. I have done it with a "sizing cell", i.e., a non-displayed UICollectionViewCell that the controller keeps only for calculating cell sizes.

There are two parts to this approach. The first part is for the collection view delegate to calculate the correct cell size, by taking in the collection view's width and using the sizing cell to calculate the cell's height.

In your UICollectionViewDelegateFlowLayout, you implement a method like this:

func collectionView(collectionView: UICollectionView,
  layout collectionViewLayout: UICollectionViewLayout,
  sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
  // NOTE: here is where we say we want cells to use the width of the collection view
   let requiredWidth = collectionView.bounds.size.width

   // NOTE: here is where we ask our sizing cell to compute what height it needs
  let targetSize = CGSize(width: requiredWidth, height: 0)
  /// NOTE: populate the sizing cell's contents so it can compute accurately
  self.sizingCell.label.text = items[indexPath.row]
  let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
  return adequateSize
}

This will cause the collection view to set the width of the cell based on the enclosing collection view, but then ask the sizing cell to calculate the height.

The second part is to get the sizing cell to use its own AL constraints to calculate the height. This can be harder than it should be, because of the way multi-line UILabel's effectively require a two-stage layout process. The work is done in the method preferredLayoutSizeFittingSize, which is like so:

 /*
 Computes the size the cell will need to be to fit within targetSize.

 targetSize should be used to pass in a width.

 the returned size will have the same width, and the height which is
 calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
 can fit within that width.

 */
 func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {

   // save original frame and preferredMaxLayoutWidth
   let originalFrame = self.frame
   let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth

   // assert: targetSize.width has the required width of the cell

   // step1: set the cell.frame to use that width
   var frame = self.frame
   frame.size = targetSize
   self.frame = frame

   // step2: layout the cell
   self.setNeedsLayout()
   self.layoutIfNeeded()
   self.label.preferredMaxLayoutWidth = self.label.bounds.size.width

   // assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width

   // step3: compute how tall the cell needs to be

   // this causes the cell to compute the height it needs, which it does by asking the 
   // label what height it needs to wrap within its current bounds (which we just set).
   let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)

   // assert: computedSize has the needed height for the cell

   // Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
   let newSize = CGSize(width:targetSize.width,height:computedSize.height)

   // restore old frame and preferredMaxLayoutWidth
   self.frame = originalFrame
   self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth

   return newSize
 }

(This code is adapted from the Apple sample code from the sample code of the WWDC2014 session on "Advanced Collection View".)

A couple points to notice. It's using layoutIfNeeded() to force layout of the entire cell, in order to compute and set the width of the label. But that's not enough. I believe you also need to set preferredMaxLayoutWidth so that the label will use that width with Auto Layout. And only then can you use systemLayoutSizeFittingSize in order to get the cell to compute its height while taking the label into account.

Do I like this approach? No!! It feels way too complex, and it does layout twice. But as long as performance doesn't become an issue, I'd rather perform layout twice at runtime than have to define it twice in code, which seems to be the only other alternative.

My hope is that eventually self-sizing cells will work differently and this will all get a lot simpler.

Example project showing it at work.

But why not just use self-sizing cells?

In theory, iOS8's new facilities for "self-sizing cells" should make this unnecessary. If you've defined a cell with Auto Layout (AL), then the collection view should be smart enough to let it size itself and lay itself out correctly. In practice, I haven't seen any examples that have gotten this to work with multi-line labels. I think this is partly because the self-sizing cell mechanism is still buggy.

But I'd bet it's mostly because of the usual trickiness of Auto Layout and labels, which is that UILabels require a basically two-step layout process. It's not clear to me how you can perform both steps with self-sizing cells.

And like I said, this is really a job for a different layout. It is part of flow layout's essence that it positions things that have a size, rather than fixes a width and lets them choose their height.

And what about preferredLayoutAttributesFittingAttributes: ?

The preferredLayoutAttributesFittingAttributes: method is a red herring, I think. That is only there to be used with the new self-sizing cell mechanism. So this isn't the answer as long as that mechanism is unreliable.

And what's up with systemlayoutSizeFittingSize:?

You're right the docs are confusing.

The docs on systemLayoutSizeFittingSize: and systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: both suggest that you should only pass UILayoutFittingCompressedSize and UILayoutFittingExpandedSize as the targetSize. However, the method signature itself, the header comments, and the behavior of the functions indicate that they are responding to the exact value of the targetSize parameter.

In fact, if you set the UICollectionViewFlowLayoutDelegate.estimatedItemSize, in order to enable the new self-sizing cell mechanism, that value seems to get passed in as the targetSize. And UILabel.systemLayoutSizeFittingSize seems to return the exact same values as UILabel.sizeThatFits. This is suspicious, given that the argument to systemLayoutSizeFittingSize is supposed to be a rough target and the argument to sizeThatFits: is supposed to be a maximum circumscribing size.

More Resources

While it is sad to think that such a routine requirement should require "research resources", I think it does. Good examples and discussions are:

Jagat Dave
  • 1,633
  • 3
  • 22
  • 30
algal
  • 26,206
  • 12
  • 73
  • 78
  • Why do you set the frame back to the old frame? – vrwim Dec 28 '15 at 13:52
  • I do that so the function can be used only to determine the preferred layout size of the view, without affecting the layout state of the view you call it in. Practically, this is irrelevant if you're holding this view only for the purpose of doing layout calculations. Also, what I do here is insufficient. Restoring the frame alone does not really restore it to its previous state, since performing layout might have modified the layouts of subviews. – algal Dec 28 '15 at 23:11
  • 1
    This is such a mess for something that people have been doing since ios6. Apple always comes out with some way but it's never quite right. – GregP Apr 05 '16 at 13:27
  • @algal This is really helpful.... kind of a noob question, but how did you create your sizingCell? Keep running into issues trying to deque. – MobileVet Apr 18 '16 at 17:41
  • Yeah, wondering the same thing. How did you create the sizing cell? – xtrinch May 04 '16 at 12:40
  • 1
    You can manually instantiate the sizing cell in viewDidLoad and hold into it view a custom property. It's only one cell so that's cheap. Since you're only using it for sizing, and you're managing all interaction with it, it does not need to participate in the collection view's cell reuse mechanism, so you don't need to get it from the collection view itself. – algal May 04 '16 at 15:40
  • @algal, Thank you for this response. It has been incredibly helpful for me. I do have a question though. I have most of this working for me, but each of my cells contains an image that I load asynchronously from the internet, and a title. The image aspect ratio is always 16:9, I want to set the width of the cell, but then have the image and label tell the cell how tall it should be. I don't want to load each image twice just to find the height of the cell. From what I am reading though, it sounds like that is the only solution... Do you have any recommendations on how to do this? – Nate4436271 May 04 '16 at 18:27
  • 1
    How on earth can self-sizing cells still be broken?! – Reuben Scratton Nov 09 '16 at 17:43
  • @ReubenScratton Maybe they've fixed it by now? If not, you're right it's a bit nuts. – algal Nov 12 '16 at 06:38
  • Where does Apple say "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."? I'm not sure I understand why it should be used only for height, and I cannot find Apple documentation on it. – RedRoses Jun 27 '17 at 05:07
  • @RedRoses I am quoting from a comment in Apple's own sample code, if I recall. – algal Jun 27 '17 at 17:28
  • Whats the situation like now that iOS 12 is out? – Pavan Jun 13 '18 at 11:07
51

There's a cleaner way to do this than some of the other answers here, and it works well. It should be performant (collection views load fast, no unnecessary auto layout passes etc), and doesn't have any 'magic numbers' like a fixed collection view width. Changing the collection view size, e.g. on rotation, and then invalidating the layout should work great too.

1. Create the following flow layout subclass

class HorizontallyFlushCollectionViewFlowLayout: UICollectionViewFlowLayout {

    // Don't forget to use this class in your storyboard (or code, .xib etc)

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
        let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes
        guard let collectionView = collectionView else { return attributes }
        attributes?.bounds.size.width = collectionView.bounds.width - sectionInset.left - sectionInset.right
        return attributes
    }

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let allAttributes = super.layoutAttributesForElementsInRect(rect)
        return allAttributes?.flatMap { attributes in
            switch attributes.representedElementCategory {
            case .Cell: return layoutAttributesForItemAtIndexPath(attributes.indexPath)
            default: return attributes
            }
        }
    }
}

2. Register your collection view for automatic sizing

// The provided size should be a plausible estimate of the actual
// size. You can set your item size in your storyboard
// to a good estimate and use the code below. Otherwise,
// you can provide it manually too, e.g. CGSize(width: 100, height: 100)

flowLayout.estimatedItemSize = flowLayout.itemSize

3. Use the predefined width + custom height in your cell subclass

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    layoutAttributes.bounds.size.height = systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    return layoutAttributes
}
Jordan Smith
  • 10,122
  • 7
  • 63
  • 111
  • 8
    This is the greatest answer I've found on the self-sizing topic. Thank you Jordan! – haik.ampardjian Oct 20 '16 at 10:54
  • 1
    Can't get this to work on iOS (9.3). On 10 it works fine. The application crashes on _updateVisibleCells (infinite recursion ?). http://www.openradar.me/25303115 – cristiano2lopes Nov 15 '16 at 17:40
  • @cristiano2lopes it works well for me on iOS 9.3. Make sure you are providing a reasonable estimate, and make sure your cell constraints are set up to resolve to a correct size. – Jordan Smith Nov 15 '16 at 18:46
  • This works amazing. I am using Xcode 8 and tested in iOS 9.3.3 (physical device) and it does not crash! Works great. Just make sure your estimate is good! – hahmed Nov 22 '16 at 05:41
  • Thank you! This is the best answer I have found on this topic. Clean and works great. – Q Liu Mar 02 '17 at 06:09
  • 1
    @JordanSmith This is not working in iOS 10. Do you have any sample demo. I have tried lot of things to do collection view cell height based on its content but nothing. I am using auto layout in iOS 10 and using swift 3. – Ekta Padaliya Mar 06 '17 at 06:27
  • @EktaPadaliya this is working great in iOS 10, in large scale App Store apps. The code above should work as a demo, I'd suggest starting a new project with a single collection view controller, and following the steps above. – Jordan Smith Mar 06 '17 at 22:28
  • Thanks @JordanSmith. This is working like a charm. Tested on iOS 10 all seems good. – Arslan Mar 28 '17 at 04:00
  • Where would you invalidate the layout? – SAHM Apr 10 '17 at 18:46
  • @JordanSmith: what is the purpose of the `copy()` located in the `layoutAttributesForItemAtIndexPath(indexPath:)` method declaration? – Imanou Petit Apr 14 '17 at 09:14
  • @imanouPetit without it, you can get a warning about some flow layout internal caching issues - the warning then suggests copying the lattributes to resolve the issue. The copy call prevents the warning. http://stackoverflow.com/questions/31508153/warning-uicollectionviewflowlayout-has-cached-frame-mismatch-for-index-path-ab – Jordan Smith Apr 14 '17 at 13:18
  • @JordanSmith: you're right. This warning appears in iOS 9 and can be solved by using `copy()`. This warning does not appear with iOS 10... – Imanou Petit Apr 14 '17 at 13:41
  • @imanouPetit given that a lot of people (myself included) will still be supporting iOS 9 with this code, I don't think it's appropriate to remove it from the answer. But yeah, if you're iOS 10+ only it's probably fine. – Jordan Smith Apr 14 '17 at 22:37
  • Would this work also with populating a collectionview bottom up? Ie first cell would appear at bottom, if you add a cell it appears at bottom, pushing other cells up. ? Similar to https://github.com/algal/ALGReversedFlowLayout – Jonny May 30 '17 at 15:06
  • Very nice! Better than the table - style layout I was using before. I have only found on problem, though - for some reason, I must scroll a bit before it sizes the cells correctly. – GoldenJoe Jun 14 '17 at 06:08
  • @GoldenJoe where are you setting the estimatedItemSize? Try doing that in `viewDidLoad()`, doing it somewhere like `viewDidAppear(animated:)` might produce the effect you're seeing. – Jordan Smith Jun 14 '17 at 20:26
  • @JordanSmith I'm trying it in `loadView`, `viewDidLoad`, and `viewDidAppear`. It never works with `estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize`. Setting `estimatedItemSize` to a hardcoded number does work if the size is less than what the cell ends up being. Otherwise it causes some kind of weird infinite loop that requires me to restart the phone to continue debugging. This seems pretty buggy...I'm almost considering going back to tableviews. Any thoughts on how this should be best implemented? – GoldenJoe Jun 14 '17 at 22:03
  • @GoldenJoe Have another look at the steps above, you should be setting the estimated size to a plausible estimate, not to `UICollectionViewFlowLayoutAutomaticSize` ;). If you're still having problems then try redoing your cell auto layout constraints - these need to be set up to resolve to the height that you want. – Jordan Smith Jun 14 '17 at 22:14
  • @JordanSmith Yeah, like I said, it always fails with `UICollectionViewFlowLayoutAutomaticSize` (why?), but if I set it to a hardcoded size it *sometimes* works and other times causes a horrible leak/crash that requires a reset (why?). I'm thinking about submitting a TSI to Apple about what's going on and how these are intended to be used, unless I can find some answers on SO. I'm out of ideas and the documentation is pretty scarce. – GoldenJoe Jun 14 '17 at 22:49
  • @GoldenJoe it fails because you're trying to provide an 'automatic' size to a variable asking for an estimated size - you need to provide a real estimated value, as per the steps above. – Jordan Smith Jun 14 '17 at 23:02
  • @JordanSmith I'm quoting Apple here: For these types of layouts, what would be really cool is if flow layout could adapt its estimate and do the math on your behalf and instead compute that estimated size from actual sizes of content that we've sized. So in iOS 10, we've got new API on flow layout to do that. All you have to do is set your flow layout.EstimatedItemSize to a new constant, UICollectionViewFlowLayout AutomaticSize. By setting your estimated item size to automatic size, you'll indicate to the CollectionViewFlowLayout that it should do the math for you. – GoldenJoe Jun 15 '17 at 02:39
  • @GoldenJoe sorry, I didn't know that. Nice - I haven't tried it, I'm still supporting iOS 9, but that's pretty cool. Perhaps it's a little buggy as you say. I'd suggest either setting a definite size for now, or redoing your layout constraints to see if that helps. – Jordan Smith Jun 15 '17 at 03:47
  • I want to add one more thing I found while experimenting with this code: it will break the positioning of section header views. For some reason, the CollectionView positions section headers as though the cells are a different size (the default?). – GoldenJoe Jun 23 '17 at 01:37
  • @GoldenJoe Hmm, I'm using headers and haven't had any issues with that. Are you setting the size of your headers in the relevant delegate method or property? – Jordan Smith Jun 23 '17 at 03:42
  • @JordanSmith What do you mean? They appear to have the correct height, but they are positioned incorrectly. I did some debugging and found that `layoutAttributesForElements` gets called three times, presumably due to auto layout. I also implemented `layoutAttributesForSupplementaryView` just to see what attributes were in there. The third (final) pass gives me good values, but it only seems to position based on the first pass. Check out this pastebin to see what I mean. Setting the section view's height only changes its height, not its position. https://pastebin.com/sLSLL23F – GoldenJoe Jun 23 '17 at 04:06
  • @GoldenJoe different heights could lead to your headers looking incorrectly positioned, depending on their layout constraints etc. Would suggest trying to reproduce your issue in a clean project - best of luck – Jordan Smith Jun 23 '17 at 05:11
  • @JordanSmith Did more debugging. The third pass on `layoutAttributesForElements ` has correct values for both item and supplementary views, but the supplementary views instead draw at the positions of the previous passes. Scrolling the views offscreen or calling reloadData() causes them to use the correct value. Any idea what's going on here? – GoldenJoe Jun 23 '17 at 11:06
6

A simple way to do it in iOS 9 in a few lines of codes - the horizontal way exemple (fixing its height to its Collection View height) :

Init your Collection View Flow Layout with an estimatedItemSize to enable self-sizing cell :

self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.estimatedItemSize = CGSizeMake(1, 1);

Implement the Collection View Layout Delegate (in your View Controller most of the time), collectionView:layout:sizeForItemAtIndexPath: . The goal here is to set the fixed height (or width) to the Collection View dimension. The 10 value can be anything, but you should set it to a value that doesn't break constraints :

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(UICollectionViewLayout *)collectionViewLayout
  sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return CGSizeMake(10, CGRectGetHeight(collectionView.bounds));
}

Override your custom cell preferredLayoutAttributesFittingAttributes: method, this part actually calculate your dynamic cell width based on your Auto Layout constraints and the height you have just set :

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
    UICollectionViewLayoutAttributes *attributes = [layoutAttributes copy];
    float desiredWidth = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].width;
    CGRect frame = attributes.frame;
    frame.size.width = desiredWidth;
    attributes.frame = frame;
    return attributes;
}
Tanguy G.
  • 1,993
  • 18
  • 18
  • Do you know if on iOS9 this simpler method works correctly when the cell includes a multi-line `UILabel`, whose line-breaking effects the intrinsic height of the cell? This is a common case which used to require all the backflips I describe. I'm wondering if iOS has fixed it since my answer. – algal Apr 18 '16 at 18:35
  • 2
    @algal Sadly, the answer is no. – jamesk Oct 06 '16 at 06:26
4

Try fixing your width in the preferred layout attributes:

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    UICollectionViewLayoutAttributes *attributes = [[super preferredLayoutAttributesFittingAttributes:layoutAttributes] copy];
    CGSize newSize = [self systemLayoutSizeFittingSize:CGSizeMake(FIXED_WIDTH,layoutAttributes.size) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    CGRect newFrame = attr.frame;
    newFrame.size.height = size.height;
    attr.frame = newFrame;
    return attr;
}

Naturally you also want to ensure that you setup your layout correctly to:

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout;
flowLayout.estimatedItemSize = CGSizeMake(FIXED_WIDTH, estimatedHeight)];

Heres something I put on Github that uses constant width cells and supports dynamic type so the height of the cells updates as the system font size changes.

Daniel Galasko
  • 21,827
  • 7
  • 71
  • 93
  • In this snippet you call systemLayoutSizeFittingSize:. Have you managed to get this to work without also setting preferredMaxLayoutWidth somewhere? In my experience, if you're not setting preferredMaxLayoutWidth, then you need to do additional manual layout, e.g., by overriding sizeThatFits: with calculations that reproduce the logic of the auto layout constraints defined elsewhere. – algal Oct 14 '14 at 14:59
  • @algal preferredMaxLayoutWidth is a property on UILabel? Not entirely sure how that is relevant? – Daniel Galasko Oct 14 '14 at 15:01
  • Oops! Good point, it's not! I've never handled this with a UITextView, so didn't realize their APIs differed there. Seems like UITextView.systemLayoutSizeFittingSize respects its frame since it has no intrinsicContentSize. But UILabel.systemLayoutSizeFittingSize ignores the frame, as it has an intrinsicContentSize, so preferredMaxLayoutWidth is needed to get intrinsicContentSize to expose its wrapped height to AL. Still seems like UITextView will need two passes or manual layout, but I don't think I understand this all fully. – algal Oct 14 '14 at 15:18
  • @algal I can agree, I have also experienced some hairy snags when using UILabel. Settings its preferred width solves the issue but it would be nice to have a more dynamic approach since this width needs to scale with its superview. Most of my projects don't use the preferred property, i generally use that as a quick fix as theres always a deeper issue – Daniel Galasko Oct 14 '14 at 15:28
0

YES it can be done using auto layout programmatically and by setting constraints in storyboard or xib. You need to add constraint for width size to remain constant and set height greater than or equal to.
http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/

http://www.cocoanetics.com/2013/08/variable-sized-items-in-uicollectionview/
Hope this will be helpful and solve your issue.

Dhaivat Vyas
  • 2,836
  • 13
  • 25
  • 3
    This should work for an absolute width value, but in the case you want the cell to always be the full width of the collection view, it won't work. – Michael Waterfall Oct 15 '14 at 23:26
  • @MichaelWaterfall I managed to get it working with full width using AutoLayout. I've added a width constraint on my content view inside the cell. Then in `cellForItemAtIndexPath` update the constraint: `cell.constraintItemWidth.constant = UIScreen.main.bounds.width`. My app is portait-only. You probably want to `reloadData` after a orientation change to update the constraint. – Flitskikker Nov 10 '16 at 11:57