Using auto-layout to calculate table cell height

Table cell height is one of the tricky bits of UITableView. You have to calculate it manually, in advance of creating your cell. Here’s a method that uses auto-layout to help you calculate it.

First of all, you’ll need a cell with its content laid out with auto-layout. The easiest way to make one is to use a xib file. Make a new xib file with only one object (a table cell) inside. Add whatever subviews you want to your table cell, and lay them out with auto-layout.

tumblr_764dbb4a706f612e8544e3a235a93fb6_52397115_400.png

Now, I’m not an auto-layout master. I’ve seldom used it before. So it took a bit of trial and error to get the layout set up correctly. The crucial thing was to make sure there were no constraints limiting the height of my labels, since I want them to resize themselves to fit whatever text goes in them.

I set up tags for my labels, so I can refer to them in code in order to populate them. I also made sure to set the labels to support multiple lines of text, by setting numberOfLines to 0 and lineBreakMode to word wrap.

Next, in your UITableViewController subclass, you need to keep a prototype UITableViewCell:

@property (nonatomic, strong) UITableViewCell *prototypeCell;

In the UITableViewController subclass’s init method, I made sure to register my cell’s nib:

self.cellNib = [UINib nibWithNibName:@"MyCustomCell" bundle:nil]; [self.tableView registerNib:self.cellNib forCellReuseIdentifier:@"CustomCell"];

In your heightForRowAtIndexPath method, create this cell if it doesn’t already exist, then populate it with data.

- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
     if (!self.prototypeCell)
     {
         self.prototypeCell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCustomCell"];
     }

    [self configureCell:self.prototypeCell forIndexPath:indexPath isForOffscreenUse:YES];

    [self.prototypeCell layoutIfNeeded];
    CGSize size = [self.prototypeCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    return size.height;
}

In this method, I’m creating a cell by dequeuing one from the table view, then calling a method to customise the cell. I do the same thing in cellForRowAtIndexPath:

- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCustomCell"];;
    [self configureCell:cell forIndexPath:indexPath isForOffscreenUse:NO];

    return cell;
}

Looking back at heightForRowAtIndexPath:, what’s going on after we have a prototype cell?

First we tell out cell to layoutIfNeeded. Then we ask it for systemLayoutSizeFittingSize:UILayoutFittingCompressedSize. This means we’re asking the layout system for the smallest size possible that will contain all the content.

So what does this configureCell method do? Whatever you want: it’s the method that applies the data to the cell. The important thing is, for the most part it doesn’t matter to that method whether it’s configuring the prototype cell used for height calculation, or configuring an actual cell that will be put on screen. For the few occasions where it does matter, I’ve added a forOffscreenUse: flag. (The only time I used the flag is when setting up key-value observing: I never want to KVO the prototype cell.)

There’s one more thing: if you have separator lines turned on, don’t forget to add 1 to the height you’ve derived! (Originally I described subclassing UILabel and adding 1 to the height returned: that was due to my misunderstanding the problem.)

And that’s it: cell heights should configure themselves to fit your content.

Note that this method isn’t that efficient. If you’re using a large table view, you’ll definitely want to implement tableView:estimatedHeightForRowAtIndexPath:. This method lets you provide a rough guess at a row height, without needing to be so accurate. You could even just return a constant number, based on the average height of your cells. If you don’t implement this method, then before it displays anything, UITableView will go through every row in your table, apply its data to the prototype cell and ask auto-layout to calculate a height, which can be very slow if there are hundreds of rows.

For more reading, have a look at this StackOverflow question.

Previous
Previous

An introduction to Cocoa Bindings

Next
Next

Predicting an Apple event