Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variable length label, tableview row heights, and auto layout

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.

enter image description here

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.

enter image description here

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?

like image 350
Ben Packard Avatar asked Nov 12 '22 23:11

Ben Packard


1 Answers

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:

enter image description here

like image 156
Ben Packard Avatar answered Nov 25 '22 09:11

Ben Packard