Praveen
Praveen

Reputation: 1851

CoreText Attributed String Height Calculation Inaccurate

CoreText isn't giving the correct height of the attributed string (its short by a line or more). I have seen a lot of posts on SO about this but unable to understand or find a solution. Can somebody explain how Core Text height calculation works? Here's an example code I wrote showing inaccurate height calculation.

Context

I have a collection view where the cell's height is determined by the content inside it. I am displaying paragraphs of text in the cells. I would like to save some performance by doing the height calculation using core text. I have seen that with core text's height calculation I could save ~300ms.

Code

// Height Calculation

+ (CGFloat)getHeight
{
    NSString *text = @"The Apple HIG recommends to use a common color for links and buttons and we did just that. By using the same color throughout the app we trained the user to always associate blue to a link.The Apple HIG recommends to use a common color for links and buttons and we did just that.By using the same color throughout the app we trained the user to always associate blue to a link.";

    NSAttributedString *attrStr = [self attributedString:text withLinespacing:3 withLineBreakMode:NSLineBreakByWordWrapping];

    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attrStr));
    CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter,
                                                                    CFRangeMake(0, attrStr.length),
                                                                    NULL,
                                                                    CGSizeMake(320, 9999),
                                                                    NULL);

    return suggestedSize.height;
}

// Load the same text when Cell is about to display

- (void)loadData
{
    NSString *text = @"The Apple HIG recommends to use a common color for links and buttons and we did just that.By using the same color throughout the app we trained the user to always associate blue to a link.The Apple HIG recommends to use a common color for links and buttons and we did just that.By using the same color throughout the app we trained the user to always associate blue to a link.";

    NSAttributedString *attrStr = [[self class] attributedString:text withLinespacing:3 withLineBreakMode:NSLineBreakByWordWrapping];

    // UILabel element
    self.textLabel.attributedText = attrStr;

    self.layer.borderColor = [UIColor blueColor].CGColor;
    self.layer.borderWidth = 1.0f;
}

// Generate attributed string with leading, font and linebreak

+ (NSAttributedString *)attributedString:(NSString *)string
                        withLinespacing:(CGFloat)linespacing
                      withLineBreakMode:(NSLineBreakMode)lineBreakMode
{
    NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:string];
    NSInteger strLength = [string length];
    NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
    style.lineSpacing = linespacing;
    style.lineBreakMode = lineBreakMode;

    [attrStr addAttributes:@{NSParagraphStyleAttributeName: style,
                         NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue" size:15]} range:NSMakeRange(0, strLength)];

    return attrStr;
}

The above code uses core text to calculate the height and UILabel to display the text. The UILabel has 3 constraints to the cell {Top:17, Leading:13px, Trailing:13px}

enter image description here

Upvotes: 0

Views: 1369

Answers (1)

Léo Natan
Léo Natan

Reputation: 57050

CTFramesetterSuggestFrameSizeWithConstraints is known to be buggy, returning incorrect height values. The missing line bug you experience is very common, and there are no good solutions that I know of, only ugly workarounds which never give 100% accurate results.

For iOS7 and above, I recommend moving to TextKit. Somehow the calculations performed there internally do work correctly, while being based on Core Text also. Using NSLayoutManager's usedRectForTextContainer: returns a correct result.

You can see a more complete answer here. While not exactly 100% on topic, there is some discussion about the bugginess of Core Text calculations.

Upvotes: 4

Related Questions