Making Smarter Table View Cells

November 18th, 2010 Posted by: Collin - posted under:Featured » Snippets

Introduction

Table Views are one of the most common things within iPhone Applications. The standard UITableViewCells that are provided by Apple are nice but have always had  a HUGE flaw in my mind. When you apply some text to the textLabel or detailTextLabel of a UITableViewCell the length of the text is not considered at all. If the text is longer than a single line you need to set the numberOfLines property to be enough so that your content can be showed. Moreover, you also need to compute the new total height of the cell to supply for the height delegate method.

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

I wrote some code that will dynamically look at what you are placing within these labels and make sure the labels have the correct number of lines and that the cells report the correct height.

from on .

GitHub

You can find this project . Please let me know any issues you may have. Happy coding!

Explanation

There are only 2 important methods in the sample project I supplied. First off I created a PLIST of a few recent Tweets from my timeline. One PLIST array of dictionaries with each dictionary having a value for “title” and for “description”. “Title” will be our textLabel text and “description” will be our detailTextLabel text. We are going to be using  methods to calculate the number of lines we need and the final height of the cell. Take a look at our first data source method below.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    static NSString *CellIdentifier = @"Cell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
    }
 
        cell.textLabel.text = [[data objectAtIndex:indexPath.row] objectForKey:@"title"];
        cell.textLabel.font = [UIFont boldSystemFontOfSize:18];
        cell.textLabel.numberOfLines = ceilf([[[data objectAtIndex:indexPath.row] objectForKey:@"description"] sizeWithFont:[UIFont boldSystemFontOfSize:18] constrainedToSize:CGSizeMake(300, MAXFLOAT) lineBreakMode:UILineBreakModeWordWrap].height/20.0);
        cell.detailTextLabel.text = [[data objectAtIndex:indexPath.row] objectForKey:@"description"];
        cell.detailTextLabel.font = [UIFont systemFontOfSize:14];
        cell.detailTextLabel.numberOfLines = ceilf([[[data objectAtIndex:indexPath.row] objectForKey:@"description"] sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(300, MAXFLOAT) lineBreakMode:UILineBreakModeWordWrap].height/20.0);
 
    return cell;
}

The real meat of this method is in calculating the number of lines. What we do here is get the string which we will place in the label, and use the sizeWithFont:constrainedToSize:lineBreakMode: to give is a height that is required. With that done, I know that each line is about 20 pixels tall, so taking the whole height divided by 20 and going to the next height integer will give us the correct number of lines. The same is done for the detail cell. If you want to use a different font, or you have a UITableViewAccessory which will make the width the label has to lay itself out in different then make sure to change those things within the methods.

Implementing the delegate method to inform the cell of its height is very similar to the logic used to devise the number of lines. In fact we use identical code but instead of dividing each by 20 we just sum them. The method in the end looks like this.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
 
        NSString *titleString = [[data objectAtIndex:indexPath.row] objectForKey:@"title"];
        NSString *detailString = [[data objectAtIndex:indexPath.row] objectForKey:@"description"];
        CGSize titleSize = [titleString sizeWithFont:[UIFont boldSystemFontOfSize:18] constrainedToSize:CGSizeMake(300, MAXFLOAT) lineBreakMode:UILineBreakModeWordWrap];
        CGSize detailSize = [detailString sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(300, MAXFLOAT) lineBreakMode:UILineBreakModeWordWrap];
 
        return detailSize.height+titleSize.height;
}
  • http://icode.dreamvision-soft.com/blog/?p=93 Making Smarter Table View Cells | iCode

    [...] Original post on iCodeBlog [...]

  • PhilipS

    That is very slick! Thanks for sharing.

  • http://www.drunknbass.com drunknbass

    the problem with your code is doing this calculation is expensive. I have gotten around this by pre computing the line height before drawing the table and storing those values. The way you have it, this expensive operation happens while drawing is taking place and every time you scroll. This would lead to chunky scrolling and having to compute those #’s needlessly.

    Even using a lazy loading method to computer these #’s on the first pass would still lead to chunky scrolling until the entire table has been scrolled through.

  • http://www.chriszamanillo.com/2010/11/making-smarter-table-view-cells/ CHRISZAMANILLO.COM :: Making Smarter Table View Cells :: http://www.chriszamanillo.com

    [...] iCodeBlog [...]

  • http://blog.marcoscrispino.com Marcos Crispino

    I think you actually don’t need to calculate the number of lines. Setting this value to zero will use as many lines as it needs.

  • http://www.hacksn0w.com jujucool290

    Very nice, good job !

  • http://fruitandrobots.wordpress.com/ Jason Nezumi

    There is one other major issue, but it’s not with this code specifically, it’s with UITableView’s implementation itself.

    The height-for-row method is used to interrogate the delegate about cell’s height BEFORE the cells are created. This means that, if we are going to have dynamic height cells for any reason whatsoever, some sort of ‘shell’ of the performLayout method we’d be calling from within the cell itself is going to be necessary.

    This quickly becomes a maintenance nightmare! Tuning cell layouts pixel-by-pixel means a minimum of two edits to ensure that these two completely different methods always return the same values. These numbers can’t be pulled directly from cells either, unless we create a cell inside this method and have it perform its layout, which is a terribly expensive operation as it will create a handful of objects and size them and calculate line sizes and so on.

    There is a possible middle-ground, that will be able to give us cell heights as cheaply as possible and still allow the method to be implemented once . . . but I’ll write up my own article for that soon. ;3