Luke Fletcher
Luke Fletcher

Reputation: 348

UITableViewCell Autolayout for Different Widths

I've setup a UITableViewCell in a storyboard which has two labels, setup as so:

-----------------------
| ------------------- |
| |     Label 1     | |
| ------------------- |
| ------------------- |
| |     Label 2     | |
| ------------------- |
-----------------------

The height of the cell is calculated as follows:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    int width = tableView.bounds.size.width - 30;

    NSString *label1Text = ....;
    NSString *label2Text = ....;

    int label1Height = ceilf([label1Text boundingRectWithSize:CGSizeMake(width, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0]} context:nil].size.height);
    int label1Height = ceilf([label2Text boundingRectWithSize:CGSizeMake(width, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0]} context:nil].size.height);

    return 15 + label1Height + 1 + label2Text + 15;
}

This works great for the width of the storyboard template (e.g. 320px). When the UITableViewController view width is made bigger (e.g. 500px+), the cell adopts the correct height, however, label 1 becomes too big for its content (spanning 1 line of text over 2 lines) and label 2 becomes squashed (spanning 2 lines of text into 1 line).

However, if I resize the UITableViewController in the storyboard (e.g. to 500px wide) and update the frames of the labels (maintaining the same constraints), when the view is loaded it looks perfect at 500px wide and terrible at 320px wide.

Can someone (who has more knowledge of autolayout) please explain this?

Upvotes: 0

Views: 645

Answers (2)

Kaleel
Kaleel

Reputation: 321

If you are using iOS7+, Try

    - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
}

Instead Of

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

Upvotes: 0

Neil
Neil

Reputation: 1853

I'm making the assumption that you intend to have multiple lines of text for each label. UILabels need to know how wide they will be before it can give an accurate height when it returns an intrinsic content size. The intended behavior can be accomplished by setting preferredMaxLayoutWidth, it may have an obvious limitation: the whole point of using Auto Layout is that you don't necessarily need or want to know what the exact values will be.

You can either manually calculate (if it's not always a fixed value) the with you want the label at some point. This is a clunky solution to me.

I prefer to create a UILabel subclass that merely overrides layoutSubviews that can automatically configure its preferredMaxLayoutWidth when being laid out.

// EBTSmartWidthLabel.h

#import <UIKit/UIKit.h>

@interface EBTSmartWidthLabel : UILabel

@end

and then

// EBTSmartWidthLabel.m

#import "EBTSmartWidthLabel.h"

@implementation EBTSmartWidthLabel

- (void)layoutSubviews
{
    self.preferredMaxLayoutWidth = 0.0f;
    [super layoutSubviews];
    self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);
    [super layoutSubviews];
}

@end

The code first sets the preferredMaxLayoutWidth to 0.0f, which makes the UILabel be as wide as possible. Then it performs the actual layout, and the width will constricted by Auto Layout constraints. It then takes the width (which should be the widest (in your case)) and makes that the preferredMaxLayoutWidth and then performs a layout again.

This isn't necessarily the most efficient way and in some rare/complex/contrived situations, may lead to lots of re-layouts, but I've had great results with it.

If you're using Storyboard/Interface Bulder, you can set a custom class on a UILabel, which will allow it to use this custom class.

Custom Class in Interface Builder

Upvotes: 1

Related Questions