Jason Tyler Jason Tyler - 1 year ago 65
iOS Question

getting estimated height of attributed string without using text view

This question pops up occasionally, but I've yet to find a reliable solution.

I want to estimate the height of an NSAttributedString accurately without having to plop the text into an invisible text view to get the true height. (The text view approach takes more processing time). But I can not get consistently reliable values from using boundingRectWithSize. It is close enough for my purposes 9 times of 10, but that's not good enough, because it occasionally results in a truncated view, with one line of the text not visible.

My sample code is below. The output is:

2016-06-27 08:54:17.106 TextHeightTest[14045:7574151] estimated1 225.000000
2016-06-27 08:54:17.108 TextHeightTest[14045:7574151] estimated2 209.000000

The first value is correct.

In the .h file:

@interface ViewController : UIViewController {
NSMutableDictionary *attributes;
UIFont *font;
UITextView *sizingTextView;
CGFloat width;

In the .m file:

- (void)viewDidLoad {
[super viewDidLoad];

sizingTextView = [[UITextView alloc] init];
font = [UIFont fontWithName:@"Helvetica" size:20];
width = 300;

NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.alignment = NSTextAlignmentLeft;
paragraphStyle.lineSpacing = 7;
paragraphStyle.maximumLineHeight = 20;
paragraphStyle.minimumLineHeight = 20;

attributes = [[NSMutableDictionary alloc] init];
[attributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
[attributes setObject:font forKey:NSFontAttributeName];

NSString *text = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
NSAttributedString *attribStr = [[NSAttributedString alloc] initWithString:text attributes:attributes];

CGFloat estimated1 = [self estimateInTextView:attribStr];
NSLog(@"estimated1 %f", estimated1);

CGFloat estimated2 = [self estimateInSpace:text];
NSLog(@"estimated2 %f", estimated2);

- (CGFloat)estimateInTextView:(NSAttributedString *)text {
sizingTextView.attributedText = text;
return [sizingTextView sizeThatFits:CGSizeMake(width, CGFLOAT_MAX)].height;

- (CGFloat)estimateInSpace:(NSString *)text {
CGRect rect = [text boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:attributes context:nil];
return CGRectGetHeight(rect);


I appreciate any help. Thanks.

Answer Source

A text view puts the text in a text container. By default, the text container is inset by 8 points on top and bottom. See the docs for textContainerInset. So, the text view will be 16 points taller than the space required by the text itself.

There might also be line-wrapping differences based on width due to the lineFragmentPadding of the text container, which defaults to 5 points on left and right. I'm not sure about that, though.

For a test, you could actually draw the string in a rect of a given size and compare that to the string rendered in a text view of that same size. See if lines are broken at different points. Draw a frame around both and see where the text is inset differently.