According to the docs, CTFramesetterSuggestFrameSizeWithConstraints ()
"determines the frame size needed for a string range".
Unfortunately the size returned by this function is never accurate. Here is what I am doing:
NSAttributedString *string = [[[NSAttributedString alloc] initWithString:@"lorem ipsum" attributes:nil] autorelease];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef) string);
CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), NULL, CGSizeMake(rect.size.width, CGFLOAT_MAX), NULL);
The returned size always has the correct width calculated, however the height is always slightly shorter than what is expected.
Is this the correct way to use this method?
Is there any other way to layout Core Text?
Seems I am not the only one to run into problems with this method. See https://devforums.apple.com/message/181450.
Edit:
I measured the same string with Quartz using sizeWithFont:
, supplying the same font to both the attributed string, and to Quartz. Here are the measurements I received:
Core Text: 133.569336 x 16.592285
Quartz: 135.000000 x 31.000000
try this.. seem to work:
+(CGFloat)heightForAttributedString:(NSAttributedString *)attrString forWidth:(CGFloat)inWidth
{
CGFloat H = 0;
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString( (CFMutableAttributedStringRef) attrString);
CGRect box = CGRectMake(0,0, inWidth, CGFLOAT_MAX);
CFIndex startIndex = 0;
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, box);
// Create a frame for this column and draw it.
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(startIndex, 0), path, NULL);
// Start the next frame at the first character not visible in this frame.
//CFRange frameRange = CTFrameGetVisibleStringRange(frame);
//startIndex += frameRange.length;
CFArrayRef lineArray = CTFrameGetLines(frame);
CFIndex j = 0, lineCount = CFArrayGetCount(lineArray);
CGFloat h, ascent, descent, leading;
for (j=0; j < lineCount; j++)
{
CTLineRef currentLine = (CTLineRef)CFArrayGetValueAtIndex(lineArray, j);
CTLineGetTypographicBounds(currentLine, &ascent, &descent, &leading);
h = ascent + descent + leading;
NSLog(@"%f", h);
H+=h;
}
CFRelease(frame);
CFRelease(path);
CFRelease(framesetter);
return H;
}
For a single line frame, try this:
line = CTLineCreateWithAttributedString((CFAttributedStringRef) string);
CGFloat ascent;
CGFloat descent;
CGFloat width = CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
CGFloat height = ascent+descent;
CGSize textSize = CGSizeMake(width,height);
For multiline frames, you also need to add the line's lead (see a sample code in Core Text Programming Guide)
For some reason, CTFramesetterSuggestFrameSizeWithConstraints()
is using the difference in ascent and descent to calculate the height:
CGFloat wrongHeight = ascent-descent;
CGSize textSize = CGSizeMake(width, wrongHeight);
It could be a bug?
I'm having some other problems with the width of the frame; It's worth checking out as it only shows in special cases. See this question for more.
The problem is that you have to apply a paragraph style to the text before you measure it. If you don't then you get the default leading of 0.0. I provided a code sample for how to do this in my answer to a duplicate of this question here https://stackoverflow.com/a/10019378/1313863.
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