lichen19853
lichen19853

Reputation: 1410

How to calculate the height of NSAttributedString, given width and number of lines?

I want to display 3 lines of NSAttributedString. Is there a way to figure out the needed height, based on width and number of lines?

And I don't want to create a UILabel to do the size calculation, since I want the calculation to be done in background thread.

Upvotes: 12

Views: 7212

Answers (3)

rpstw
rpstw

Reputation: 1712

There is a method in TTTAttributedLabel called

+ (CGSize)sizeThatFitsAttributedString:withConstraints:limitedToNumberOfLines:

Basically,this method use some Core Text API to calculate the height, the key function is

CGSize CTFramesetterSuggestFrameSizeWithConstraints(
    CTFramesetterRef framesetter,
    CFRange stringRange,
    CFDictionaryRef __nullable frameAttributes,
    CGSize constraints,
    CFRange * __nullable fitRange )

which I think ,is also used by

- (CGRect)textRectForBounds:limitedToNumberOfLines:


this is a workaround and I think there are better way...

static UILabel *label;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    label = [UILabel new];
});
label.attributedText = givenAttributedString;
CGRect rect = CGRectMake(0,0,givenWidth,CGFLOAT_MAX)
CGFloat height = [label textRectForBounds:rect
                        limitedToNumberOfLines:2].size.height;

Upvotes: 0

tropicalfish
tropicalfish

Reputation: 1270

The answer by @dezinezync answers half of the question. You'll just have to calculate the maximum size allowed for your UILabel with the given width and number of lines.

First, get the height allowed based on number of lines:

let maxHeight = font.lineHeight * numberOfLines

Then calculate the bounding rect of the text you set based on the criteria:

let labelStringSize = yourText.boundingRectWithSize(CGSizeMake(CGRectGetWidth(self.frame), maxHeight),
            options: NSStringDrawingOptions.UsesLineFragmentOrigin,
            attributes: [NSFontAttributeName: font],
            context: nil).size

Upvotes: 5

dezinezync
dezinezync

Reputation: 3012

I wonder why this is still unanswered. Anyhow, here's the fastest method that works for me.

Make an NSAttributedString Category called "Height". This should generate two files titled "NSAttributedString+Height.{h,m}"

In the .h file:

@interface NSAttributedString (Height)  
-(CGFloat)heightForWidth:(CGFloat)width;  
@end  

In the .m file:

-(CGFloat)heightForWidth:(CGFloat)width  
{  
    return ceilf(CGRectGetHeight([self boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
                                                    options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading
                                                    context:nil])) + 1;  
}

Here's what's happening:

  1. boundRectWithSize:options:context get's a rect constrained to a width you pass to the method. The NSStringDrawingUsesLineFragmentOrigin option tells it to expect multiline string.
  2. Then we fetch the height parameter from that rect.
  3. In iOS 7, this method returns decimals. We need a round figure. ceilf helps with that.
  4. We add an extra unit to the returning value.

Here's how to use it

NSAttributedString *string = ...
CGFloat height = [string heightForWidth:320.0f];

You can use that height for your layout computations.

Upvotes: 6

Related Questions