dergab
dergab

Reputation: 965

Dynamic UITableViewCell's height in iOS 7 AND iOS 8 impossible

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...

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

Upvotes: 3

Views: 812

Answers (2)

Jean Le Moignan
Jean Le Moignan

Reputation: 22236

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: ...];.

Upvotes: 0

dergab
dergab

Reputation: 965

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.

Upvotes: 2

Related Questions