There is a lot of good info on SO and elsewhere about how to calculate the necessary height for a UITableView row depending on the size of a subview. The typical example, and the one I'm applying here, is a cell containing a label with variable length text.
The usual advice is to use the sizeWithFont:constrainedToSize:lineBreakMode:
in the tableview's heightForRowAtIndexPath:
. But most examples I've seen use a 'magic' number based on the known width of the label.
CGSize constraint = CGSizeMake(224.0, MAXFLOAT); //224 is known to be the width available
CGSize labelSize = [cellText sizeWithFont:cellFont constrainedToSize:constraintSize lineBreakMode:NSLineBreakByWordWrapping];
Some better examples might use the tableview's bounds, and then subtract magic numbers or constants for the other views in the cell, which is at least a little clearer (and works better if the bounds change on rotation etc).
CGSize constraint = CGSizeMake(self.tableview.bounds.size.width - 20 - 10 - 36 - 20, MAXFLOAT); //each non-label width subtracted
CGSize labelSize = [cellText sizeWithFont:cellFont constrainedToSize:constraintSize lineBreakMode:NSLineBreakByWordWrapping];
This is the common approach, but it seems lacking. Who's to say the widths of the other cell subviews, and even the font, are guaranteed not to change (or will always be diligently changed in both places)?
I decided I would rather have the cell be responsible for providing this height. The cell always knows its attributes. To accomplish this, I first made all of my layout code in the cell refer to some local constants. I then added a class method to my custom cell class, which can return the required height for any given piece of text, based on its own font and sizing constants.
+ (CGFloat)heightRequiredForText:(NSString *)text usingWidth:(CGFloat)width
{
//a class method that tells the caller how much height is needed for the provided text, based on the cell's size and font constants
CGSize constraintSize = CGSizeMake(width - kMargin - kGap - kMargin - kCheckboxWidth, MAXFLOAT);
CGRect bounds = [text boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:self.labelFont}
context:nil];
return bounds.size.height;
}
This allows the UITableViewController to simply provide the text and the current bounds' width, (i.e the width of the tableview). The font and layout can be changed in the cell subclass and everything continues to 'just work'.
Question 1
Just as a side note, is this reasonable? Is there a better way to achieve the same thing?
Question 2
Here's the main issue I am having with this (and the more standard) approach. When the cell is in its edit mode, the width of the label changes to accommodate editing accessories. Since the row height is constant, and the label is using auto layout, its height increases.
The required behavior is for the height of the label to remain constant and truncation should be used when necessary.
Where and how can I implement this? Should I be doing something in the cell's setEditing:? Maybe adding another constraint to restrict the height, and turning on truncation, with the opposite applied when editing == NO? What about the 'swipe to delete' pattern - I don't think this triggers setEditing? What about layoutSubview - could I do something in there?
I tried many things - the closest I got was overriding layoutSubviews, testing if the user had switched in to edit mode, and if so capturing the intrinsic height of the label first and then adding a temporary constraint to enforce it (which was then removed when coming out of edit mode). It worked well except for a few edge cases.
But then I started thinking about auto layout some more. What was I trying to achieve? I didn't want the label to grow taller when its width was reduced. That's a pretty straightforward constraint:
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.label
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self.contentView
attribute:NSLayoutAttributeHeight
multiplier:1
constant:0]];
Adding this additional constraint works well. Note that I also switch the line break mode to truncate on edit:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With