Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIView aspect ratio confuses systemLayoutSizeFittingSize

Alright, another UITableViewCell dynamic height problem, but with a little twist. Unfortunately I can't jump to iOS 8 only when released, otherwise the problem would be solved. Need iOS >= 7.1.

I'm trying to achieve a cell with two images on the top of the cell, a title label below them and a description label below that. I know the two top images will be square and thus I want them to be of the same size and to keep the square aspect ratio but resize when the screen size varies (like orientation change or different device).

An image that might help with the visualization (can't include because of rep. Nevermind the colors, visualization help): http://i.stack.imgur.com/kOhkl.png

Notice the gap after the last text, that's not supposed to be there.

I implemented dynamic height in previous application according to: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights (with success), and used the same method now as well.

I have setup the constraints using both storyboards and programmatically but with no success.

Here's the kicker though. When calling:

CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

In:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

the height value is way bigger than the height when reading:

cell.contentView.frame

Another issue is that if I remove the aspect ratio constraints on the images and only changes it to an explicit height constraint it works fine.

For anyone willing to help I put together a really simple sample project illustrating the problem: https://github.com/alefr/DynamicTableCellHeight

It's setup to use storyboards for constraints and there's an extra branch: ExplicitHeightConstraint that does nothing but change the aspect ration constraint to a height constraint for the images.

**So the question is: ** Can anyone see anything wrong with the constraints that makes it confuse the height calculations, or do anyone have any alternative suggestions to get the wanted result? (Although I'd like to use auto layout for the views).

There's quite a few constraints in the works (although nothing really fancy) so I think it's easier to look in the provided storyboard (github link) than stating them explicitly here. But if someone fancies that I can do that as well. The estimated height calculations look like:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

// Don't care about memory leaks now:
DynamicTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"dynamicCell"];

[self populateCell:cell withContent:self.tableData[indexPath.row]];

// Make sure the constraints have been set up for this cell, since it may have just been created from scratch.
// Use the following lines, assuming you are setting up constraints from within the cell's updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

// Set the width of the cell to match the width of the table view. This is important so that we'll get the
// correct cell height for different table view widths if the cell's height depends on its width (due to
// multi-line UILabels word wrapping, etc). We don't need to do this above in -[tableView:cellForRowAtIndexPath]
// because it happens automatically when the cell is used in the table view.
// Also note, the final width of the cell may not be the width of the table view in some cases, for example when a
// section index is displayed along the right side of the table view. You must account for the reduced cell width.
cell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));

// Do the layout pass on the cell, which will calculate the frames for all the views based on the constraints.
// (Note that you must set the preferredMaxLayoutWidth on multi-line UILabels inside the -[layoutSubviews] method
// of the UITableViewCell subclass, or do it manually at this point before the below 2 lines!)
[cell setNeedsLayout];
[cell layoutIfNeeded];

// Get the actual height required for the cell's contentView
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

// So we can read debug values
CGRect contentFrame = cell.contentView.frame;
CGRect firstImageFrame = cell.firstArtwork.frame;
CGRect secondImageFrame = cell.secondArtwork.frame;
CGRect nameFrame = cell.name.frame;
CGRect numArtworksFrame = cell.numArtworks.frame;

// Add an extra point to the height to account for the cell separator, which is added between the bottom
// of the cell's contentView and the bottom of the table view cell.
height += 1.0f;
return height;
}

- (void)populateCell:(DynamicTableViewCell*)cell withContent:(DynamicContent*)content
{
    cell.firstArtwork.image = content.firstImage;
    cell.secondArtwork.image = content.secondImage;
    cell.name.text = content.name;
    cell.numArtworks.text = [NSString stringWithFormat:@"%@ artworks", content.numImages];
}

Thanks

like image 714
alefr Avatar asked Sep 04 '14 11:09

alefr


1 Answers

I was in the same situation, and solved it to override the systemLayoutSizeFittingSize function in the given cell and replace the aspectRation constraint to a height constraint. In the following example the mediaPlayerAspectRationConstraint is added to the view during the view lifecycle and the mediaPlayerHeightContraint is going to use to determine the right height of the cell (see the code below).

So, I used the

CGFloat height = [cell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

instead of the [cell.contentView systemLayoutSizeFittingSize:].

- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize {
    self.mediaPlayerHeightContraint.constant = CGRectGetHeight(self.experienceMedia.frame);

    //Lets replace it with an exact height constraint (workaround).

    //To remove the Unable to simultaneously satisfy constraints. exceptions
    [self.contentView layoutIfNeeded];

    [self.experienceMedia removeConstraint:self.mediaPlayerAspectRationConstraint];
    [self.experienceMedia addConstraint:self.mediaPlayerHeightContraint];

    [self setNeedsUpdateConstraints];

    CGSize res = [self.contentView systemLayoutSizeFittingSize:targetSize];

    [self.contentView layoutIfNeeded];

    [self.experienceMedia removeConstraint:self.mediaPlayerHeightContraint];
    [self.experienceMedia addConstraint:self.mediaPlayerAspectRationConstraint];

    [self setNeedsUpdateConstraints];

    return res;
}
like image 128
Balazs Nemeth Avatar answered Nov 07 '22 16:11

Balazs Nemeth