UITableViewCell height with UITextView

You will find lots of resources on the internet about calculating the height of a UITableViewCell based on an inner UITextView. Most of what I’ve found when I was trying to find a solution was either incomplete or wrong. Here are my learnings and the approach I’m taking to tame what gave me headaches for some time now.

Prerequisites

I wanted this to work for both table view styles within universal apps that support autorotation. The solution should also be flexible enough to allow for subclassing and configuration of the custom table view cell so that I can reuse the code for different kinds of cells.

UITableViewCell

To calculate the height of the cell (most likely to be used in tableView: heightForRowAtIndexPath:) you have to know its width so that you can calculate the size of the UITextView with the method sizeWithFont:forWidth: lineBreakMode: provided by the NSString class. That is because the naive approach of adding the cell to the table view and asking for the frame size of the text view does not work - tableView:heightForRowAtIndexPath: gets called before tableView:cellForRowAtIndexPath: so that you have to do the calculation on your own.

UITextView

First of all we have to notice that an UITextView has an inset of 8px on each side. I have not found official documetation about this and event though its contentInset property is set to UIEdgeInsetZero by default, it has this 16px horizontal padding we have to subtract when calculating with the text views width.

Note: On StackOverflow and elsewhere you can find remarks about reduzing the padding by applying a custom inset with negative values, like this:

textView.contentInset = UIEdgeInsetsMake(-8,-8,0,0);

This indeed unsets the visual padding, but from what I have seen and tested it does not make the text run wider - you still have to subtract the 16px. With this example the text just seems to start 8px more to the left, but still wraps at the original width.

The approach

There is one thing I don’t really like about the solution I’ve found and that is passing in the table view to the method - (CGFloat)heightForTableView:(UITableView *)tableView that does the calculation. However I did not find a way to avoid this, because we need the width of the cells outer frame, because it will give us a way to get to the cells width - this way it works with autorotation and cell reuse.

The code for the custom UITableViewCell subclass (TextCell) looks like this:

@implementation TextCell

// UITextView has an inset of 8px on each side
- (CGFloat)textInset {
    return 8.0f;
}

// Sets the text and adjusts the frame height
// so that the text view does not scroll
- (void)setContentText:(NSString *)theText {
    self.textView.text = theText;
    CGRect frame = self.textView.frame;
    CGFloat cHeight = self.textView.contentSize.height;
    frame.size.height = cHeight + self.textInset;
    self.textView.frame = frame;
}

// Calculates the text views width by subtracting
// the horizontal insets and margins
- (CGFloat)textWidthForOuterWidth:(CGFloat)outerWidth {
    CGFloat textInset = self.textInset * 2;
    CGFloat marginH = self.marginLeft + self.marginRight;
    CGFloat width = outerWidth - marginH;
    CGFloat textWidth = width - textInset;
    return textWidth;
}

- (CGFloat)heightForTableView:(UITableView *)tableView {
    // calculate the outer width of the cell
    // based on the tableView style
    CGFloat outerWidth = tableView.frame.size.width;
    if (tableView.style == UITableViewStyleGrouped) {
        // the grouped table inset is 20px on
        // the iPhone and 90px on the iPad
        BOOL iPad = [UIDevice currentDevice].userInterfaceIdiom ==
          UIUserInterfaceIdiomPhone;
        outerWidth -= iPad ? 20.0f : 90.0f;
    }
    CGFloat textInset = self.textInset * 2;
    CGFloat marginV = self.marginTop + self.marginBottom;
    CGFloat textWidth = [self textWidthForOuterWidth:outerWidth];
    // use large value to avoid scrolling
    CGFloat maxHeight = 50000.0f;
    CGSize constraint = CGSizeMake(textWidth, maxHeight);
    CGSize size = [self.textView.text
        sizeWithFont:self.textView.font
        constrainedToSize:constraint
        lineBreakMode:UILineBreakModeWordWrap];
    CGFloat height = size.height + textInset + marginV;
    return height;
}

@end

You may have noticed, that the code above refers to getters for the margin. I use these to configure the coordinates that text views in different kinds of TextCell subclasses may have.

Cellconfiguration in Interface Builder

For example I would use these settings in a concrete subclass when the UITextView in Interface Builder is set like in the screenshot above.

- (CGFloat)marginTop {
    return 65.0f;
}

- (CGFloat)marginBottom {
    return 40.0f;
}

- (CGFloat)marginLeft {
    return 1.0f;
}

- (CGFloat)marginRight {
    return 1.0f;
}

The code and examples for this are taken from the iOctocat codebase.

iPhone app for GitHub

iOctocat

is GitHub in your pocket: The go to app for staying up to date with your projects on your iPhone, and iPod Touch.
It is