12

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.

lichen19853
  • 1,410
  • 2
  • 14
  • 21
  • There are way to do that in other question - http://stackoverflow.com/questions/42171468/nsattributedstring-height-limited-by-width-and-numberoflines/42216094#42216094 – Arsynth Feb 14 '17 at 00:56

3 Answers3

6

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.

dezinezync
  • 2,730
  • 21
  • 15
  • 2
    @ezinezync you just calculate the height within the specified width, but how to limit the height to 3 lines??? – 0oneo Sep 25 '14 at 03:23
  • @0oneo for that, you'd have to limit the `numberOfLines` on the label itself. I can't think of an alternate way to do that right now. – dezinezync Sep 25 '14 at 14:34
  • 1
    This isn't answering the question, the main problem being the `numberOfLines` which is really hard since NSAttributedString can have all sort of content (even images). To me it seems really hard to solve. – Fantattitude Nov 10 '15 at 09:38
5

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
tropicalfish
  • 1,230
  • 1
  • 16
  • 29
0

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;
rpstw
  • 1,582
  • 14
  • 16