I encountered the same problem when trying to do the same.
The problem is how the UITextView trigger line breaks compared to boundingRectWithSize. You can read more details here: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextLayout/Concepts/CalcTextLayout.html
But you can calculate the exact size! Basically, there are two UITextView properties that need to be considered in order to get the correct size estimates. The first is textContainer.lineFragmentPadding , the second is textContainerInset .
Firstly, textContainer.lineFragmentPadding : You may have noticed that your size is usually disabled by 10px , because the default value for the system is 5px . When you calculate your estimated size, you need to subtract this value from the size you are checking for and add it back when you have the final value.
Secondly, textContainerInset . This is the UIEdgeInset that you will need to add to your final calculated value according to the systems.
This is code based on how I solved the problem:
- (CGSize)sizeThatFits:(CGSize)size CGFloat lineFragmentPaddings = self.textContainer.lineFragmentPadding * 2; CGFloat horzPadding = self.textContainerInset.left + self.textContainerInset.right + lineFragmentPaddings; CGFloat vertPadding = self.textContainerInset.top + self.textContainerInset.bottom; size.width -= horzPadding; CGRect boundingRect = [attributedText boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin context:nil]; size = boundingRect.size;
Note. CGSizeRound is just a custom function I wrote that rounds width and height from CGSize to the nearest 0.5 .
For comparison, if you create a second UITextView and make sure that the values ββof textContainer.lineFragmentPadding and textContainerInset same, you should see values ββthat are almost identical to the nearest 0.5 .
And to your question about calculating the correct pointSize, this is some pseudo code for this:
CGFloat pointSize = 64; CGFloat minPointSize = 32; CGFloat decrementor = 4; CGFloat padding = self.textContainerInset.left + self.textContainerInset.right + lineFragmentPaddings; CGFloat actualWidth = self.maxTextViewSize.width - padding * 2; CGRect boundingRect = CGRectZero; BOOL isValidPointSize = NO; do { if (pointSize < minPointSize) { pointSize = minPointSize; boundingRect.size.height = self.maxTextViewSize.height; isValidPointSize = YES; } else { NSDictionary *defaultAttributes = [self.customTextStorage defaultAttributesForPointSize:pointSize]; NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string attributes:defaultAttributes]; boundingRect = [attrString boundingRectWithSize:CGSizeMake(actualWidth, 1024) options:NSStringDrawingUsesLineFragmentOrigin context:nil];
Again, the above pseudo-code is based on my implementation (not intended to just replace what you have in return). Hope this helps!