Making Smarter Table View Cells

November 18th, 2010 Posted by: - 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.

Smarter Table View Cells from Collin Ruffenach on Vimeo.

GitHub

You can find this project now on GitHub. 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 NSString UIKit Additions Reference 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;
}