In the code below, CTFramesetterSuggestFrameSizeWithConstraints
sometimes returns a CGSize
with a height that is not big enough to contain all the text that is being passed into it. I did look at this answer. But in my case the width of the text box needs to be constant. Is there any other/better way to figure out the correct height for my attributed string? Thanks!
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attributedString);
CGSize tmpSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), NULL, CGSizeMake(self.view.bounds.size.width, CGFLOAT_MAX), NULL);
CGSize textBoxSize = CGSizeMake((int)tmpSize.width + 1, (int)tmpSize.height + 1);
This was driving me batty and none of the solutions above worked for me to calculate layout of long strings over multiple pages and return range values with completely truncated-out lines. After reading the API notes, the parameter in CTFramesetterSuggestFrameSizeWithConstraints for 'stringRange' is thus:
'The string range to which the frame size will apply. The string range is a range over the string that was used to create the framesetter. If the length portion of the range is set to 0, then the framesetter will continue to add lines until it runs out of text or space.'
After setting the string range to 'CFRangeMake(currentLocation, 0)' instead of 'CFRangeMake(currentLocation, string.length)', it all works perfectly.
CTFramesetterSuggestFrameSizeWithConstraints works correctly. The reason that you get a height that is too short is because of the leading in the default paragraph style attached to attributed strings. If you don't attach a paragraph style to the string then CoreText returns the height needed to render the text, but with no space between the lines. This took me forever to figure out. Nothing in the documentation spells it out. I just happened to notice that my heights were short by an amount equal to (number of lines x expected leading). To get the height result you expect you can use code like the following:
NSString *text = @"This\nis\nsome\nmulti-line\nsample\ntext."
UIFont *uiFont = [UIFont fontWithName:@"Helvetica" size:17.0];
CTFontRef ctFont = CTFontCreateWithName((CFStringRef) uiFont.fontName, uiFont.pointSize, NULL);
// When you create an attributed string the default paragraph style has a leading
// of 0.0. Create a paragraph style that will set the line adjustment equal to
// the leading value of the font.
CGFloat leading = uiFont.lineHeight - uiFont.ascender + uiFont.descender;
CTParagraphStyleSetting paragraphSettings[1] = { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof (CGFloat), &leading };
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(paragraphSettings, 1);
CFRange textRange = CFRangeMake(0, text.length);
// Create an empty mutable string big enough to hold our test
CFMutableAttributedStringRef string = CFAttributedStringCreateMutable(kCFAllocatorDefault, text.length);
// Inject our text into it
CFAttributedStringReplaceString(string, CFRangeMake(0, 0), (CFStringRef) text);
// Apply our font and line spacing attributes over the span
CFAttributedStringSetAttribute(string, textRange, kCTFontAttributeName, ctFont);
CFAttributedStringSetAttribute(string, textRange, kCTParagraphStyleAttributeName, paragraphStyle);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(string);
CFRange fitRange;
CGSize frameSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, textRange, NULL, bounds, &fitRange);
CFRelease(framesetter);
CFRelease(string);
CTFramesetterSuggestFrameSizeWithConstraints()
is broken. I filed a bug on this a while back. Your alternative is to use CTFramesetterCreateFrame()
with a path that is sufficiently high. Then you can measure the rect of the CTFrame that you get back. Note that you cannot use CGFLOAT_MAX
for the height, as CoreText uses a flipped coordinate system from the iPhone and will locate its text at the "top" of the box. This means that if you use CGFLOAT_MAX
, you won't have enough precision to actually tell the height of the box. I recommend using something like 10,000 as your height, as that's 10x taller than the screen itself and yet gives enough precision for the resulting rectangle. If you need to lay out even taller text, you can do this multiple times for each section of text (you can ask CTFrameRef for the range in the original string that it was able to lay out).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With