3

I'm having a hard time trying to get a table view with dynamically sized cells to be layed out well under iOS 7 and 8. I can't detail all the differences that are to find (in the means of "ways of broken layouts") between iOS 7 and 8 which can be produced using the different "tweaks", "workarounds" and whatever I've found here and elsewhere. But in the end either on iOS 7 or on iOS 8 (if not both) some or all cell's content is misaligned because the layout system "breaks" one of the custom constraints to "recover".

Basically I have three different types of content. And as I show these contents not only in the aforesaid table view, I wrapped the contents in three subclasses of UIView. Let's call them SummaryViews For the table view then I created three subclasses of UITableViewCell of which each adds the according SummaryView to its contentView and sets self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight. On updateViewConstraints I generally remove all "my" constraints that might have been added previously and do...

- (void)updateConstraints
{
   // ...removed custom constraints before
    NSDictionary *views = NSDictionaryOfVariableBindings(_summaryView);

    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_summaryView]-|"
                                                                       options:0
                                                                       metrics:nil
                                                                         views:views]
                        toView:self.contentView];

    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[_summaryView]-|"
                                                                       options:0
                                                                       metrics:nil
                                                                         views:views]
                        toView:self.contentView];
    [super updateConstraints];
}

In tableView:estimatedHeightForRowAtIndexPath: I return a static estimate depending on the content's type. In tableView:heightForRowAtIndexPath: I use "prototype" cells in the manner of...

// ..set content on prototype cell before
[prototypeCell setNeedsUpdateConstraints];
[prototypeCell layoutIfNeeded];
CGSize size = [prototypeCell systemLayoutSizeFittingSize:UILayoutFittingExpandedSize];
return size.height;

Here usually the debugger breaks on UIViewAlertForUnsatisfiableConstraints (on [prototypeCell layoutIfNeeded]) with <NSLayoutConstraint:0x17409f180 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x17019f070(44)]> conflicting with the rest of the constraints.

So I tried...

  • Setting tableView.rowHeight = UITableViewAutomaticDimension in table view's viewDidLoad
  • Not implementing tableView:estimatedHeightForRowAtIndexPath:
  • Using...

    if (NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1) {
        cell.contentView.frame = cell.bounds;
        cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
    }
    

    ...before applying cell's content and calculating its layout

  • Doing self.contentView.bounds = CGRectMake(0.0, 0.0, 1000, 1000); upon cell initialization
  • Probably other things which, right now, I can't remember as I'm on this for more or less two days now.

It's always the same, if I achieve a variant which doesn't complain about unsatisfiable constraints, the layout is usually messed up nevertheless. And although I get the table view not to set the "default" <NSLayoutConstraint:0x17409f180 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x17019f070(44)]> constraint, the constraint set instead (on basis of the height returned by tableView:heightForRowAtIndexPath:) neither suits the layout system.

This post is kind of a last resort. I know that without the concrete view classes' constraints you can't re-check them. But as I'm able to use these views's outside of a table view without problems, it shouldn't be a subview's constraint's problem.

I think I will have fall back to calculate the sizes of all view elements manually (on basis of [UIScreen mainScreen].bounds) and set them directly (as widths and heights) to all subviews. So I'm able to get the concrete height of a cell and can set the contentView's frame manually. Quite a pity as it messes up the layout code remarkably....

Best regards, gabriel

dergab
  • 965
  • 7
  • 16
  • 1
    you are on the wrong path, your cells' heights are based on the value which you return via the `–tableView:heightForRowAtIndexPath:` method. that will define the actual height on the screen. playing around with auto-layout or auto-resizemask does not help on. – holex Nov 25 '14 at 16:16
  • 2
    @holex Your hunting reputations? **A**: Feel free to enlighten me (and others) on what the _correct_ path would be. **B**: I think there has never been any doubt on what `tableView:heightForRowAtIndexPath` is for. **C**: I'm not _playing around_ with auto layout, I'm actually using it. **D**: _playing around_ with `autoresizingMask` seems to have helped helped others: http://stackoverflow.com/a/19154287/981728. – dergab Nov 25 '14 at 16:57

2 Answers2

2

In the end I found an acceptable solution, which doesn't mess up layout code to much. In short:

In table view cell's updateConstraints I removed the constraint from _summaryView-bottom to superview-bottom. So height constraints on the content view do not interfere with the ones from summary view.

- (void)updateConstraints
{
    NSDictionary *views = NSDictionaryOfVariableBindings(_summaryView);

    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_summaryView]-|"
                                                                       options:0
                                                                       metrics:nil
                                                                         views:views]
                        toView:self.contentView];

    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[_summaryView]"
                                                                       options:0
                                                                       metrics:nil
                                                                         views:views]
                        toView:self.contentView];
    [super updateConstraints];
}

In tableView:heightForRowAtIndexPath: I just use intrinsicContentSize of the cell in question to get the height:

        [prototypeCell.summaryView applyStuff];
        [prototypeCell layoutIfNeeded];
        height = [prototypeCell intrinsicContentSize].height;

The implementation of intrinsicContentSize of said table view cell looks as follows:

- (CGSize)intrinsicContentSize
{
    // Calculate the available content width if not done yet
    static CGFloat availableWidth = 0.0;
    if (availableWidth == 0.0) {
        availableWidth = CGRectGetWidth([UIScreen mainScreen].bounds);
    }

    // Check if the contentView's frame needs an update
    if (CGRectGetWidth(self.contentView.frame) != availableWidth) {
        CGRect frame = CGRectMake(0.0, 0.0, availableWidth, 100.0);
        self.contentView.frame = frame;
    }

    [_summaryView layoutIfNeeded];
    CGSize size = _summaryView.frame.size;
    size.height += 2.0 * V_PADDING;
    size.width += 2.0 * H_PADDING;
    return size;
}

Notice that for apps that support portrait and landscape mode availableWidth either must be reset on orientation change or it shouldn't be static. The *_PADDING's are the space I want the _summaryView to have on all sides.

Further I removed self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth from cell's initialization code. As it doesn't seem to have any noticeable effect.

dergab
  • 965
  • 7
  • 16
  • I feel your frustration, I'm using a similar approach: without setting the trailing constraint, and by applying self.view.frame.width (tableViewCell is always ZER0 due to `UIView-Encapsulated-Layout-Width`) to the subview's frame to calculated its height... Was hoping to see if anyone can come up with an approach which let the contentView frame to be purely calculated by intrinsicContentSize, seems there is still no possible solution. – vk.edward.li Nov 30 '14 at 16:18
  • My tableView cell has a large UILabel, in `tableView:heightForRowAtIndexPath:`, I'm using `contentLabelHeight = contentLabel.font.sizeOfString(contentLabel.text!, constrainedToWidth: Double(self.view.frame.width - 44)).height` to calculated the height first (p.s. 44 is your `2.0 * H_PADDING`) and then return contentLabelHeight + contentLabel.frame.origin.y + 20 (p.s. 20 is your `2.0 * V_PADDING`) – vk.edward.li Nov 30 '14 at 16:20
  • Yes, it seems there is no _nice_ way of handling "dynamic row height" under iOS 7. – dergab Dec 01 '14 at 09:15
0

I might come a little late in the discussion, but I had a similar problem and found the solution.

When adding your constraints, add them to the contentView and not to the cell. Otherwise, the "|-" and "-|" in the visual language format will create constraints related to the cell itself, and our poor contentView will have no constraints whatsoever.

So, instead of [self addConstraints: ...];, use [self.contentView addConstraints: ...];.

Jean Le Moignan
  • 22,158
  • 3
  • 31
  • 37
  • `_summaryView` is a child view of the cell's `contentView`. When adding constraints to a view it doesn't matter if the constraints refer to this very view or a sub-view of it. – dergab Jun 12 '15 at 17:11
  • I've just double-checked to make sure if it does a difference (in my current project) -- and it does. Any visual language constraint with "|-" or "-|" refers to the container receiving the addConstraint method. I also get the message "Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view", which I don't get if I send the "addConstraint" to the contentView instead of the cell. Under iOS8.3. – Jean Le Moignan Jun 12 '15 at 18:48
  • Well, this would be an explanation. Sadly I have currently no time to test that with our project. But I'll keep it in mind and sooner or later I'll check it... – dergab Jun 15 '15 at 06:24