4

I have a UITableView with dynamic height UITableViewCells and I'm using auto layout to calculate the heights as per this excellent answer on the topic. So far so good.

I'm adapting the app to work with the larger iPhone (6 and 6 Plus) screen sizes and it's mostly pretty straightforward.

However, in some of my cells I have an image that I want to span the whole width of the cell, and I want the image's height to be proportional to the width of the image (0.55 * the width, to be specific). Up to now I had the width and height of the image hard coded in the auto layout constraints, based on the standard 320px portrait table view width pre iPhone 6/6 Plus.

I thought it would be straightforward to add a relative height constraint like so (I'm using PureLayout):

[self.myImage autoMatchDimension:ALDimensionHeight 
                     toDimension:ALDimensionWidth 
                          ofView:self 
                  withMultiplier:0.55f 
                        relation:NSLayoutRelationGreaterThanOrEqual];

If you're not familiar with PureLayout, this translates to a call to

[NSLayoutConstraint constraintWithItem: attribute: relatedBy: toItem: attribute: multiplier: constant:0.0f]

There are other constraints that pin it's edges to the superview, which is the UITableViewCell contentView.

However, when I call systemLayoutSizeFittingSize: on the cell's contentView it seems to totally ignore the relative height constraint, and the resulting cell height is far too small to fit the image.

If I set an explicit height constraint instead of a relative one, there's no problem. Similarly, if I subclass UIImageView and return an explicit size in intrinsicContentSize, there is no problem.

I even tried the following in my UIImageView subclass:

- (void) layoutSubviews {
    [super layoutSubviews];    
    self.intrinsicSizeForAutolayout = self.frame.size;    
    [super layoutSubviews];
}

- (CGSize)intrinsicContentSize {
    return self.intrinsicSizeForAutolayout;
}

where intrinsicSizeForAutolayout is a property I defined for the purpose. I thought this might work similarly to the way that setting preferredMaxLayoutWidth for UILabels solves a similar problem.

But no. It doesn't work.

It would seem I have little alternative to use ugly screen width checking code to conditionally set a fixed height constraint depending on the screen width, something I really wanted to avoid, as it kind of defeats the purpose of using auto layout in the first place.

Community
  • 1
  • 1
mluisbrown
  • 14,448
  • 7
  • 58
  • 86
  • Before you call `systemLayoutSizeFittingSize:` on the offscreen cell's contentView, are you setting the offscreen cell's width to the actual width of the table view at that time (which will be > 320pt on a 6 & 6 Plus)? If that doesn't help, I recommend trying to use the iOS 8 self sizing cell mechanism instead to see if that resolves things. If that doesn't work either, I'm guessing there might be some issue with your constraints...if you could modify one of the [sample projects](https://github.com/smileyborg/TableViewCellWithAutoLayoutiOS8) to demonstrate the issue that would be most helpful. – smileyborg Nov 21 '14 at 17:57
  • I am setting the offscreen cell's width to the actual width of the table view. I want to maintain iOS 7 compatibility so I haven't tried the iOS 8 self sizing cell mechanism. I'm going to try to get you a sample project to demo the issue. – mluisbrown Nov 22 '14 at 21:06
  • @smileyborg I tried to reproduce the problem in the iOS 7 sample project without success. I did however discover that in my case, if I use Xcode 6 view debugging, the `UIImageView` has constraints based on its content size, whereas in the the modified sample project, it doesn't. I should also add that I'm actually using your UIView-AutoLayout library (the predecessor of PureLayout), not PureLayout itself, all though I doubt that would make a difference. – mluisbrown Nov 23 '14 at 01:33
  • First off, no there won't be any issue with using the older UIView+AutoLayout library -- it creates the same constraints in the end. If you're having trouble reproducing/recreating the issue with the sample project, that suggests that there's some issue with your specific setup --likely a constraints issue. As far as iOS 7 vs 8, it should be very easy to conditionalize your code to use the self-sizing cell approach when running on iOS 8 -- anyways, I do recommend trying self sizing cells to see if the issue still persists, as that can help isolate a constraint issue vs something else. – smileyborg Nov 24 '14 at 02:17
  • @smileyborg when using the self-sizing cell approach running on iOS 8 it all works fine. btw, ensuring that `tableView:heightForRowAtIndexPath:` is not called when running on iOS 8 is not *totally* straight forward, but [this answer](http://stackoverflow.com/a/26022247/368085) did the trick. However, I'm still not sure what's not working on iOS 7. The fact it works using the iOS 8 approach would indicate that the constraints are OK... – mluisbrown Nov 26 '14 at 12:40
  • @smileyborg OK, I've managed to reproduced the problem in the iOS 7 sample project: https://github.com/mluisbrown/TableViewCellWithAutoLayout It's most obvious if you delete the model and add a single row. You'll also see the auto layout constraint warnings in the console. I would really appreciate it if you took a look. – mluisbrown Nov 26 '14 at 18:19
  • Sorry for the delay in responding - was on vacation. Anyways, I took a look at your project and found the issue. The width of the cell's `contentView` doesn't any have constraints by default, so when you call `systemLayoutSizeFittingSize:` on it, the constraint solver assumes it's valid to compress the width as much as needed, which of course results in the wrong height. If you add a single constraint to the `contentView` fixing its width at the final table view width, things start working because that constraint is factored into the cell sizing layout pass. – smileyborg Dec 03 '14 at 17:21
  • See here for the fix: https://gist.github.com/smileyborg/0a2082a4d26fcc7fde4d – smileyborg Dec 03 '14 at 17:30
  • @smileyborg just tested your fix in my actual project (not the sample) and it works! I had given up and resorted to using a fixed height constraint when running iOS 7. Since the larger iPhones all run iOS 8 that wasn't a problem. However, with your fix I don't need that OS version check when setting the cell constraints. Never occurred to me to set a contentView constraint in `tableView:heightForRowAtIndexPath:`. Feel free to post your solution as an answer which I'll accept right away :) – mluisbrown Dec 03 '14 at 18:04

1 Answers1

7

The width of the cell's contentView doesn't any have constraints by default (its width is set by the SDK only when it is added to the table view), so when you call systemLayoutSizeFittingSize: on it, the constraint solver assumes it's valid to compress the width as much as needed when trying to find a valid solution, which of course results in the wrong height.

To fix this, you can add a single constraint to the contentView that fixes its width to the final cell/table view width. This works because that constraint will be factored into the cell sizing layout pass and result in systemLayoutSizeFittingSize: working as expected.

Using PureLayout, I recommend doing something like:

[UIView autoSetPriority:UILayoutPriorityRequired - 1 forConstraints:^{
    [cell.contentView autoSetDimension:ALDimensionWidth toSize:CGRectGetWidth(tableView.bounds)];
}];

Note that it's not a bad idea to set a priority of less than Required for the constraint, just because this is simply to aid the sizing operation, and you don't want an exception if this gets broken (perhaps by a future SDK change in how table view cells work). But this probably doesn't matter.

Also, as usual you probably want to make sure this constraint is only added once (not every time tableView:heightForRowAtIndexPath: is called) -- this should be easy to do.

See here for a specific solution to your fork of the example project: https://gist.github.com/smileyborg/0a2082a4d26fcc7fde4d

smileyborg
  • 30,197
  • 11
  • 60
  • 73