20

I use UITableView with cells created using UITableViewCellStyleSubtitle. Every cell's height is dynamically adjusted using the

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

delegate method.

The problem you can see on the picture

(note: image updated)

http://img.skitch.com/20090715-g7srxgee2d7fhi5rab2wufrdgm.png

How to set up align of textLabel and detailTextLabel to the top of the cell? (I really don't wont to do it by subclassing UITableViewCell and overriding layoutSubviews)

Thanx

Igor
  • 1,258
  • 2
  • 14
  • 20

4 Answers4

36

Well, this one cost me an afternoon, but I think I figured it out. As far as I can tell, this appears to be a bug in how UITableViewCell is laying out the textLabel and detailTextLabel. When you set the row height, it seems to allocate equal height to the two labels, which means that you get exactly the behavior you're seeing above, even though detailTextLabel needs more room. Here are the two things I did to fix the problem. I had to subclass UITableViewCell to fix it, but it's a minimal amount of code.

First, make sure you're calculating the height of each row properly. Put this method into your table view delegate. Replace the font methods with your own:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
   NSString *cellDetailText = [[self itemForIndexPath: indexPath] detailDescription];
   NSString *cellText = [[self itemForIndexPath: indexPath] description];
   // The width subtracted from the tableView frame depends on:
   // 40.0 for detail accessory
   // Width of icon image
   // Editing width
   // I don't think you can count on the cell being properly laid out here, so you've
   // got to hard code it based on the state of the table.
   CGSize constraintSize = CGSizeMake(tableView.frame.size.width - 40.0 - 50.0, CGFLOAT_MAX);
   CGSize labelSize = [cellText sizeWithFont: [self cellTextLabelFont] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
   CGSize detailSize = [cellDetailText sizeWithFont: [self cellDetailTextLabelFont] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];

   CGFloat result = MAX(44.0, labelSize.height + detailSize.height + 12.0); 
   return result;
}

Then, subclass UITableViewCell and override layoutSubviews:

#import "UITableViewCellFixed.h"


@implementation UITableViewCellFixed
- (void) layoutSubviews {
   [super layoutSubviews];
   self.textLabel.frame = CGRectMake(self.textLabel.frame.origin.x, 
                                      4.0, 
                                      self.textLabel.frame.size.width, 
                                      self.textLabel.frame.size.height);
   self.detailTextLabel.frame = CGRectMake(self.detailTextLabel.frame.origin.x, 
                                           8.0 + self.textLabel.frame.size.height, 
                                           self.detailTextLabel.frame.size.width, 
                                           self.detailTextLabel.frame.size.height);
}
@end
Chris Garrett
  • 4,824
  • 1
  • 34
  • 49
  • 1
    Great answer, worked perfectly. Nice touch: replace 4.0 with 12.0 if detailTextLabel.text == nil to center the textLabel vertically in the view when there is no detailText for the cell. – Gerry Shaw Mar 11 '10 at 06:43
  • 2
    One suggestion: It's really worth using variables to store the frames and use these in your calls to `CGRectMake`. You'll save 14 redundant method calls in just this small method. These redundant method calls can sum up and have a performance impact. – DarkDust Jan 27 '12 at 09:02
8

Here is another solution without subclassing cells, so it certainly works with different table styles. (I haven't checked other solutions.) The title and detail strings are declared in my UITableViewController header and already defined. They aren''t very long, certainly well within height 4000!

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

    CGRect frame = [UIScreen mainScreen].bounds;
    CGFloat width = frame.size.width;

    int section = indexPath.section;
    int row = indexPath.row;

    NSString *title_string = [title_strings_array objectAtIndex:row];
    NSString *detail_string = [detail_strings_array objectAtIndex:row];

    CGSize title_size = {0, 0};
    CGSize detail_size = {0, 0};

    if (title_string && [title_string isEqualToString:@""] == NO ) {
        title_size = [title_string sizeWithFont:[UIFont systemFontOfSize:22.0]
                              constrainedToSize:CGSizeMake(width, 4000)
                                  lineBreakMode:UILineBreakModeWordWrap];
    }

    if (detail_string && [title_string isEqualToString:@""] == NO ) {
        detail_size = [detail_string sizeWithFont:[UIFont systemFontOfSize:18.0]
                                constrainedToSize:CGSizeMake(width, 4000)
                                    lineBreakMode:UILineBreakModeWordWrap];
    }

    CGFloat title_height = title_size.height;
    CGFloat detail_height = detail_size.height;

    CGFloat content_size = title_height + detail_height;

    CGFloat height;

    switch ( section ) {

        case 0:
            height = content_size;
            break;

        //Just in case  
        default:
            height = 44.0;
            break;

    }

    return height;

}
Ken
  • 30,811
  • 34
  • 116
  • 155
1

However you're sizing your cells, you should do it with the various sizing methods of NSString. That way, you can determine exactly how tall to make the cell and avoid the whitespace.

Jeff Kelley
  • 19,021
  • 6
  • 70
  • 80
-2

If it turns out that the textLabel and detailTextLabel are laid out using autoresizing masks, maybe you can do this when you return the cell:

cell.textLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin;
cell.detailTextLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

If this works, it's a bit easier than subclassing the cell. I haven't tried it though.

Daniel Dickison
  • 21,832
  • 13
  • 69
  • 89