2

I'm trying resize a label dynamically according to text height. The height can vary from 0 to many lines in the UILabel. I've come up with a solution for this problem that works fine on iOS 8 but fails on iOS 7.1 which I'm trying to support as well.

Autolayout is not being used in this project and all constraints are done programatically.

The code is as follows:

//TableDelegate.m

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
  return 85.0f;
}

//CustomTableViewCell.m

-(UILabel *)commentTextLabel
{
  if(!_commentTextLabel)
  {
    _commentTextLabel = [UILabel new];
    _commentTextLabel.numberOfLines = 0;
    _commentTextLabel.translatesAutoresizingMaskIntoConstraints = NO;
  }

  return _commentTextLabel;
}




  -(void)setupViews
    {
      [self.contentView addSubview:self.profilePictureView];
      [self.contentView addSubview:self.userName];
      [self.contentView addSubview:self.timePublishedLabel];
      [self.contentView addSubview:self.commentTextLabel];
      [self.contentView addSubview:self.seeMoreButton];

      self.backgroundColor = [UIColor salooteInputTextBg];
      self.contentView.backgroundColor = [UIColor salooteInputTextBg];

      NSDictionary *views = @
      {
        @"picture"       : self.profilePictureView,
        @"userName"      : self.userName,
        @"timePublished" : self.timePublishedLabel,
        @"text"          : self.commentTextLabel,
        @"seeMore"       : self.seeMoreButton
      };

      [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-5-[picture(38)]-5-[userName]-5-[timePublished]-5-|" options:0 metrics:nil views:views]];
      [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[picture]-5-[text]-5-|" options:0 metrics:nil views:views]];
      [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-5-[seeMore]-5-|" options:0 metrics:nil views:views]];
      [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-5-[userName]-5-[text]-5-[seeMore]-5-|" options:0 metrics:nil views:views]];
      [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-5-[picture(38)]" options:0 metrics:nil views:views]];

    }

  -(void)updateConstraints
  {
      [super updateConstraints];
   }

iOS 8 result (left) iOS 7.1 result (right)

enter image description here enter image description here I'm not setting any height constraint in my code for the UILabel but rather trying to let the constraints adjust the vertical height for me. If anyone has some input on how to make this work properly on iOS 7.1 I would really appreciate it.

Moving constraints into setupViews produces this: (iOS 7.1 top iOS 8 bottom)

enter image description here

enter image description here

zic10
  • 2,222
  • 4
  • 27
  • 51

2 Answers2

0

It seems to me you're not adding vertical constraints to the commentTextLabel? You only have this:

//Comment text
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[picture]-5-[text]-0-|" options:0 metrics:nil views:views]];

Try setting a vertical constraint as well--it's likely that you're getting insufficient constraints errors and iOS 8 is guessing the height better than iOS 7. Also, if you're adding constraints to the views, you shouldn't have to call sizeToFit inside the getter.

Autolayout is not being used in this project and all constraints are done programatically.

You're still using Autolayout even if you're adding the constraints only programatically. :)


In response to edits

Your vertical height constraint is insufficient--you only specified the height of the commentTextLabel but not its y-coordinate. Remember that the main objective in Autolayout is to provide a complete set of constraints such that iOS can compute for a view's x, y, width, and height.

I think your constraints are screwed up overall. :) Try adding these rules to the content view instead (I just used 5 for any padding):

H:|-5-[picture(38)]-5-[username]-5-[timePublished]-5-|
H:[picture]-5-[text]-5-|
H:|-5-[seeMore]-5-|
V:|-5-[username]-5-[text]-5-[seeMore]-5-|
V:|-5-[picture(38)]

Also, add your constraints in setupViews--you should only have to add your constraints once and ONLY modify them in updateConstraints. I think updateConstraints is called every time layoutSubviews is called so your constraints keep getting added every time the cell's layout is refreshed.


In response to edits

Your label's word wrap style must be set, too. From inside the commentTextLabel, add

_commentTextLabel.lineBreakMode = NSLineBreakByWordWrapping;

Always set that in conjunction to numberOfLines = 0 if you want a UILabel with a dynamic height.

You also need to right-align your seeMore label (it occupies the full width of the cell minus the padding) by setting that label's alignment property.

And try providing a bigger faux height for now--perhaps 150 or 200 instead of 85, just so we can see all the elements.

For the timePublished label, I forgot to indicate the following vertical constraint:

V:|-5-[timePublished]
MLQ
  • 12,329
  • 12
  • 79
  • 130
  • Thanks for the suggestion Matt. I modified the post and removed the sizeToFit and added a vertical height constraint for the comment text. This prevents the crash from happening but distorts the resizing for iOS 8 and still doesn't work properly with iOS 7. I'm guessing I need to implement heightForRow for iOS 7 but not really sure how to tie it together nicely with iOS 8. I might need a different approach entirely. – zic10 Jan 06 '15 at 00:29
  • You *should* implement heightForRow. :) iOS 8 (again) just seems better at guessing the cell's height but in iOS 7, you can't expect your cells to compute their own heights. Can you try returning a constant height from heightForRow for now and see what happens? – MLQ Jan 06 '15 at 00:35
  • I've updated the post and added the heightForRow. I'm returning 85 but that is just a best guess variable. The real height could be larger or smaller but limited to 500 char in total. The problem I'm running into with this on both version of iOS now the comment text is pushed too far down. I'll update the photos. – zic10 Jan 06 '15 at 00:39
  • Updated my answer in response to your update. Moved constraints into setup views. It seems though even the constraints are cleaner, that 85 returned in heightForRow is imposing a hard limit on the cell height that is not being adjusted by auto layout. – zic10 Jan 06 '15 at 01:21
0

I have found that the only way to support both iOS7 and iOS8 easily, is to do the height calculations for each cell yourself using off screen prototypes. The following is an excellent article on the issues. I could find no way to mix auto layout height calculation from iOS8 with manual height estimates for iOS7 in a single code base.

Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

The only issue I had with this method was when I used size classes to change the cell font sizes so I could have larger font on iPad etc... This issue is discussed here:

Offscreen UITableViewCells (for size calculations) not respecting size class?

Community
  • 1
  • 1
Rory McKinnel
  • 7,730
  • 2
  • 15
  • 28
  • I actually got the dynamic layout working on both iOS versions from the SO article linked above. I had to resort to a dirty hack to check for iOS version in code at run time and use appropriate values. If I remember correctly, I only had to make that distinction on estimatedRowHeight parameter. You can find the differences between the two linked projects and combine them into one. – Numan Tariq Feb 03 '15 at 16:58
  • I may well have missed a step. I was concentrating more on the size class issues. Good thing is that once the manual calculation is in, it works in both versions. You just don't get the auto feature, which is really just doing what you are doing yourself. If memory serves, the problem I had was that as soon as I implemented heightForRowAtIndexPath, that iOS8 reverted to using that instead of its internal auto layout even though I set the tableView row height to Auto. I will try it on my next tableview controller. – Rory McKinnel Feb 03 '15 at 17:46
  • 1
    You are absolutely right about the heightForRowAtIndexPath. To get around that issue, I have if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) { return UITableViewAutomaticDimension; } and when initializing the table view I use: if(SYSTEM_VERSION_LESS_THAN(@"8.0")){ self.tableView.estimatedRowHeight = UITableViewAutomaticDimension; } else if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) { self.tableView.rowHeight = UITableViewAutomaticDimension; self.tableView.estimatedRowHeight = AVERAGE_CELL_HEIGHT; } – Numan Tariq Feb 04 '15 at 09:38