10

I've been toying with dynamic UICollectionViewCell's and have noticed that on iOS 8 calling cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) on a UICollectionViewCell returns an incorrect width. Currently the only workaround that addresses this is to explicitly add a width constraint to force the cell's width. The below code is used in a Cell subclass:

func calculatePreferredHeightForFrame(frame: CGRect) -> CGFloat {
    var newFrame = frame
    self.frame = newFrame
    let widthConstraint = NSLayoutConstraint(item: contentView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: CGRectGetWidth(frame))
    contentView.addConstraint(widthConstraint)
    self.setNeedsUpdateConstraints()
    self.updateConstraintsIfNeeded()
    self.setNeedsLayout()
    self.layoutIfNeeded()
    let desiredHeight: CGFloat = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    newFrame.size.height = CGFloat(ceilf(Float(desiredHeight)))
    contentView.removeConstraint(widthConstraint)
    return CGRectGetHeight(newFrame)
}

I know that with iOS 8 and dynamic UICollectionViewFlowLayout that the UICollectionViewCell's contentView handles constraints differently but is there something I'm missing here? What does one need to do to ensure that systemLayoutSizeFittingSize uses a particular width on a cell?

I also came across this post (Specifying one Dimension of Cells in UICollectionView using Auto Layout) and believe this might actually invalidate my question. Perhaps UICollectionViewFlowLayout is not designed for cells with only one dynamic dimension, but that still doesn't explain why the cell gives an unusual width.

Community
  • 1
  • 1
Daniel Galasko
  • 21,827
  • 7
  • 71
  • 93
  • I have the exact same problem, my cell width is always returned as 320 (xib view size). Can't really work around it... – Kaan Dedeoglu Apr 01 '15 at 14:33
  • 3
    Do you have multi-lined UILabel in this cell? If so you probably have wrong preferred max layout width. – hris.to Apr 23 '15 at 14:26
  • @hris.to if you read the question again you will see it has nothing to do with labels but rather the size of the entire cell – Daniel Galasko Apr 23 '15 at 14:27
  • @DanielGalasko sure I understand this. It just happens to me few days back with UITableViewCell. If preferedMaxLayoutWidth is set let's say to 240px it'll act like a constraint and when received systemLayoutSizeFittingSize it'll return bigger width than actual frame width. You have to subclass label and override setBounds to update preferredMaxLayoutWidth according to new label width(update it only if values are different, and after super setBounds call). – hris.to Apr 23 '15 at 14:32
  • Please let me know if you have troubles implementing this solution, so I'll leave an answer with complete solution. – hris.to Apr 23 '15 at 14:35
  • @hris.to That is true but sadly still not the case. I should probably get a GitHub example going. But it fails even if the cell only contains images. See the linked question :) – Daniel Galasko Apr 23 '15 at 15:16
  • OK I'm eager to check out a minimalistic gitHub example. The only problem with images that comes to mind is if you connect an imageView to both left and right edges of your cell. However I had to admit that I did this only with UITableViewCells, never with UICollectionViewCell. Do you have same problems with tableview(some dumb variant of your current layout positioned in TableView)? – hris.to Apr 23 '15 at 15:30
  • @hris.to no, table view cells are much easier since their width is fixed to the tableViews bounds. I was trying to create a similar behaviour in flow layout and it doesn't seem to support that without writing your own – Daniel Galasko Apr 23 '15 at 15:33

3 Answers3

9

I've faced same issue. The key is using priorities when getting systemLayoutSize.

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    if (!self.prototypeCell) {
        self.prototypeCell = [TrainingMenuCell new];
        self.prototypeCell.contentView.translatesAutoresizingMaskIntoConstraints = NO;
    }

    [self configureCell:self.prototypeCell forIndexPath:indexPath];
    [self.prototypeCell setNeedsLayout];
    [self.prototypeCell layoutIfNeeded];

    CGFloat width = CGRectGetWidth(collectionView.frame) - 12;
    CGSize fittingSize = UILayoutFittingCompressedSize;
    fittingSize.width = width;

    CGSize size = [self.prototypeCell.contentView systemLayoutSizeFittingSize:fittingSize withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityDefaultLow];
    return size;
}
Timur Bernikovich
  • 4,733
  • 3
  • 39
  • 53
  • the subtraction part is not working for me. I subtract 20 from the collectionview width as I have section inset of 10 on left and right. But I still end with 320 not 300. – nr5 Sep 07 '17 at 08:04
  • @Nil have you checked `fittingSize` and `size` variable? Are priorities copied from my answer? – Timur Bernikovich Sep 07 '17 at 08:33
  • Nevermind. actually I was using self.prototypeCell instead of self.prototypeCell.contentView. That's why it broke. – nr5 Sep 07 '17 at 08:34
  • Since we are talking on the same subject, do you have any idea for how to set the height of a horizontal collection view to the height of the maximum height cell in the list? – nr5 Sep 07 '17 at 08:35
  • @Nil you should do this on your own. Each time you reload CV, calculate sizes separately to determine max height. Or maybe custom CVL can help you. – Timur Bernikovich Sep 07 '17 at 08:43
2

In my case, it was not calculating width correctly because I was missing those two lines before calling systemLayoutSizeFittingSize method:

[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];

Spent hours to discover it so hope it could be helpful for others having similar issue.

Pei
  • 10,336
  • 4
  • 35
  • 38
1

A SSCCE (example) is available at my repository:

https://github.com/knutvalen/sandbox-uicollectionview

Below is a snippet of the main code.

Swift 4

var array = ["foo", "bar", "baz"]

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)

    if let myCell = cell as? MyCollectionViewCell {
        myCell.titleLabel.text = array[indexPath.item]
        myCell.subtitleLabel.text = "my subtitle for " + array[indexPath.item] + " is a very long text with many letters and words"
        return myCell
    }

    return cell
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let forcedWidth: CGFloat = 200
    let myCell = MyCollectionViewCell()
    myCell.titleLabel.text = array[indexPath.item]
    myCell.subtitleLabel.text = "my subtitle for " + array[indexPath.item] + " is a very long text with many letters and words"
    myCell.subtitleLabel.preferredMaxLayoutWidth = forcedWidth - (myCell.myMargin * 2)
    let optimalSize = myCell.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
    let forcedSize = CGSize(width: forcedWidth, height: optimalSize.height)
    return forcedSize
}

Output

Vertical Horizontal

Knut Valen
  • 259
  • 2
  • 14
  • This does not actually answer my question. I am trying to explicitly force a width on cells. ie trying to create a UITableView with a collection view. If you try your above code you will see it wont work :) – Daniel Galasko Apr 09 '18 at 15:05
  • @DanielGalasko You are right, of course. I did not understand your problem correctly. Now I updated the answer to enforce a width on all cells. – Knut Valen Apr 11 '18 at 20:22
  • I think you need to try that code out properly before posting it. You calculate the size for your cell before even setting a width meaning the cell can probably be any width for all we know. Also try testing it in landscape and iPad etc. See https://stackoverflow.com/questions/26143591/specifying-one-dimension-of-cells-in-uicollectionview-using-auto-layout to truly understand my problem but I don't think a solution that quick makes sense, do you? – Daniel Galasko Apr 15 '18 at 09:38
  • @DanielGalasko I updated my answer with a fully working Xcode project to remove any misconseptions. Please have look at https://github.com/knutvalen/sandbox-uicollectionview for a fully working example. – Knut Valen May 27 '18 at 18:45
  • @mihirmehta how do you try to increase it? – Knut Valen Jul 27 '18 at 07:42
  • By increasing text in subtitle at both place – Mihir Mehta Jul 27 '18 at 09:09
  • @mihirmehta Thank you for the feedback. I updated the answer now to support multiline subtitle label. – Knut Valen Sep 02 '18 at 12:37