views:

257

answers:

2

By default, a UITableViewCell instance positions the label pair textLabel/detailTextLabel in the center of its parent view. I prefer to have the pair aligned to the top of the cell. How do I do that programmatically?

Thanks,
Doug

A: 

Layout of the UITableViewCell textLabel and detailTextLabel are not directly modifiable, except by picking one of the defined styles provided by the API.

typedef enum {
   UITableViewCellStyleDefault,
   UITableViewCellStyleValue1,
   UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
} UITableViewCellStyle;

If you want to customize UITableViewCell layout, you'll need to subclass it and override the -layoutSubviews method.


- (void) layoutSubviews {
   // [super layoutSubViews]; // don't invoke super

   ... do your own layout logic here
}
Bill Garrison
@Bill, layoutSubviews is a method I have never overridden and is poorly documented. Could you elaborate a bit? Thanks.
dugla
+1  A: 

The docs describe -[UIView layoutSubviews] as

"Overridden by subclasses to layout subviews when layoutIfNeeded is invoked. The default implementation of this method does nothing."

That description is not elaborate, but is accurate. In this case, the method's behavior is to layout your subviews. It will be invoked anytime the device orientation changes. It is also scheduled for later invocation whenever you call -setNeedsLayout.

Since, UIView's implementation does nothing, (and I presume the same for UIControl), you get total creative freedom to make your UIView subclass subviews be positioned wherever you want them.

In subclassing a UITableViewCell, you have a couple of options to try:

  1. Override -layoutSubviews and
    1. manipulate the position of the built-in textLabel and -detailLabel views.
  2. Override -viewDidLoad,
    1. create two of your own UILabels to provide the text and detailed text,
    2. add them to self.contentView, and
    3. override -layoutSubviews to manipulate the position of your custom UILabel views

In a related SO question, the recommendation is to avoid #1, manipulating the built-in textLabel and detailTextLabel.

A more reliable bet would be to do something like this:


@interface MyTableViewCell : UITableViewCell {
    UILabel *myTextLabel;
    UILabel *myDetailTextLabel;
}
// ... property declarations
@end

@implementation MyTableViewCell 
@synthesize myTextLabel, myDetailTextLabel;

- (id) initWithFrame: (CGRect)frame {
    self = [super initWithFrame: frame];
    if (self) {
        myTextLabel = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease];
        [self.contentView addSubview: myTextLabel];

        myDetailTextLabel = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease];
        [self.contentView addSubview: myDetailTextLabel];
    }
    return self;
}

- (void) dealloc {
    [myTextLabel release];
    [myDetailTextLabel release];
    [super dealloc];
}

- (void) layoutSubviews {

    // Let the super class UITableViewCell do whatever layout it wants. 
    // Just don't use the built-in textLabel or detailTextLabel properties
    [super layoutSubviews]; 

    // Now do the layout of your custom views

    // Let the labels size themselves to accommodate their text
    [myTextLabel sizeToFit];
    [myDetailTextLabel sizeToFit];

    // Position the labels at the top of the table cell
    CGRect newFrame = myTextLabel.frame;
    newFrame.origin.x = CGRectGetMinX (self.contentView.bounds);
    newFrame.origin.y = CGRectGetMinY (self.contentView.bounds);
    [myTextLabel setFrame: newFrame];

    // Put the detail text label immediately to the right 
        // w/10 pixel gap between them
    newFrame = myDetailTextLabel.frame;
    newFrame.origin.x = CGRectGetMaxX (myTextLabel.frame) + 10.;
    newFrame.origin.y = CGRectGetMinY (self.contentView.bounds);
    [myDetailTextLabel setFrame: newFrame];
}

@end

In MyTableViewCell, you would ignore the built-in text labels and use your own custom UILabels. You take complete control over positioning them within the content rect of the table view cell.

I'm leaving a lot of stuff out. In doing custom layout with text labels, you'd want to consider:

  • Figuring out your own layout algorithm.

    I'm using a layout algorithm above that resizes the custom UILabels to fit their text content, then positions them side-by-side. You'll likely want something more specific to your app.

  • Keeping the custom labels within the content view.

    In -layoutSubviews, you might want logic to keep the custom UILabels sized and positioned so that they don't fall outside the bounds of the content rect. With my naive layout logic, any long text dropped into either UILabel (or both) could cause the labels to be positioned right out of the content view bounds.

  • How to handle -viewDidLoad/-viewDidUnload.

    As coded above, this subclass doesn't handle being loaded from a nib. You might want to use IB to layout your cell, and if you do, you'll need to think about -viewDidLoad/-viewDidUnload/-initWithCoder:

Bill Garrison
@Bill, Thanks for the super thorough illumination of layoutSubviews. Had no idea it was that easy to use. Cool.
dugla