Following on from a previous article about NIBs and UITableRowCell implementation, I thought I would share a little knowledge about how to create variable height UITableRowCell classes.

First up, let's take a little look at the method we need to use to achieve this. Under the UITableViewDelegate protocol there is the tableView:heightForRowAtIndexPath: method. The idea of this is to do as it says on the tin, return the height of the row at this index path.

So, how to work out this value. My preferred solution is to have a static method on the class that extends UITableRowCell. For this example, I will create a simple two-line implementation of a table row cell called TwoLineTableRowCell (for help on how to do this, please refer to the previous article). There will be two iVars in this class (in addition to the UILabel ivars): messageOne and messageTwo.

Our static method in this case will have the following signature:


+ (CGFloat) heightOfCell:(NSString *)messageOne messageTwo:(NSString *)messageTwo;


This method will be called within the tableView:heightForRowAtIndexPath: method in the UITableViewDelegate. In our example, we will have an array of example objects with a name and title properties, so the implementation will be


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
ExampleObject *example = [myExampleObjectArray objectAtIndex:indexPath.row];

return [TwoLineTableRowCell heightOfCell:example.name messageTwo:example.title];

}


Now, the implementation of our static method will be as follows:


+ (CGFloat) heightOfCell:(NSString *)messageOne messageTwo:(NSString *)messageTwo {
CGFloat lineOneHeight = [messageOne sizeWithFont:[UIFont boldSystemFontOfSize:17.0]
constrainedToSize:CGSizeMake(280, 1000)
lineBreakMode:UILineBreakModeWordWrap].height;

CGFloat lineTwoHeight = [messageOne sizeWithFont:[UIFont systemFontOfSize:17.0]
constrainedToSize:CGSizeMake(280, 1000)
lineBreakMode:UILineBreakModeWordWrap].height;

return lineOneHeight + lineTwoHeight + 28.0; // 28.0 is a little padding if required
}


Great, all done? Not quite. With this implementation, the cell will take up the correct height, but the UILabels will still be at the same location. The first thing to do is to update the #lines setting for each UILabel to 0. In addition to this, the prepareForReuse and layoutSubviews methods need updating.

The prepareForReuse method needs to reset the frames of the UILabel instances back to normal.


messageOneLabel.frame = CGRectMake(kLabelOneX, kLabelOneY, kLabelOneWidth, kLabelOneHeight);
messageTwoLabel.frame = CGRectMake(kLabelTwoX, kLabelTwoY, kLabelTwoWidth, kLabelTwoHeight);


Now, in the layoutSubviews method, you will do something along the lines of:


CGSize msgOneSize = [messageOne sizeWithFont:[UIFont boldSystemFontOfSize:17.0]
constrainedToSize:CGSizeMake(280, 1000)
lineBreakMode:UILineBreakModeWordWrap];
CGSize msgTwoSize = [messageTwo sizeWithFont:[UIFont systemFontOfSize:17.0]
constrainedToSize:CGSizeMake(280, 1000)
lineBreakMode:UILineBreakModeWordWrap];

CGFloat twoDiff = messageTwpLabel.frame.origin.x + messageOneLabel.frame.origin.x;

messageOneLabel.frame = CGRectMake(messageOneLabel.frame.origin.x, messageOneLabel.frame.origin.y,
messageOneLabel.frame.size.width, msgOneSize.height);

messageTwoLabel.frame = CGRectMake(msgOneSize.height + twoDiff, messageTwoLabel.frame.origin.y,
messageTwoLabel.frame.size.width, msgTwoSize.height);


Obviously you will have to set the content of the UILabels, but you should know how to do this by now!