Why is boundingRectWithSize wrong when using UIFont preferredFontForTextStyle?

652 views Asked by At

I have found that a call to boundingRectWithSize is extremely incorrect, missing an entire additional line, when called with NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody]. However, using the font [UIFont fontWithName:@"Helvetica Neue" size:17.f], it is just fine.

Here is test code showing the bug:

- (void)viewDidLoad {
    [super viewDidLoad];

    NSString *measureText = @"I'm the king of Portmanteaus ... My students slow clap";

    CGSize maxSize = CGSizeMake(226.f, CGFLOAT_MAX);

    UIFont *targetFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];

    CGRect stringRect = [measureText boundingRectWithSize:maxSize
                                                         options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
                                                      attributes:@{ NSFontAttributeName : targetFont }
                                                         context:nil];        

    UILabel *drawLabel = [[UILabel alloc] initWithFrame:stringRect];
    drawLabel.font = targetFont;
    [self.view addSubview:drawLabel];
    drawLabel.textColor = [UIColor blackColor];
    drawLabel.text = measureText;
    drawLabel.numberOfLines = 0;
}

And the output:

enter image description here

However, if I make targetFont = [UIFont fontWithName:@"Helvetica Neue" size:17.f];

It renders properly:

enter image description here

In the first case, the size is {226.20200000000008, 42.281000000000006}, but in the correct second case the size is {228.64999999999998, 60.366999999999997}. Therefore, this is not a rounding issue; this is missing an entire new line.

Is it wrong to pass [UIFont preferredFontForTextStyle:UIFontTextStyleBody] as an argument into boundingRectWithSize?


Edit

Per the comments in the answer below, I believe there is a bug with how iOS treats three periods in connection with the proceeding word. I have added this code:

measureText = [measureText stringByReplacingOccurrencesOfString:@" ..." withString:@"…"];

and it works properly.

1

There are 1 answers

8
matt On BEST ANSWER

A label adds a little margin round the outside of the text, and you have not allowed for that.

If, instead, you use a custom UIView exactly the size of the string rect, you will see that the text fits perfectly:

enter image description here

Here's the code I used. In the view controller:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *measureText = @"I'm the king of Portmanteaus ... My students slow clap";
    CGSize maxSize = CGSizeMake(226.f, CGFLOAT_MAX);
    UIFont *targetFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
    CGRect stringRect = [measureText boundingRectWithSize:maxSize
                                                  options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
                                               attributes:@{ NSFontAttributeName : targetFont }
                                                  context:nil];
    stringRect.origin = CGPointMake(100,100);
    StringDrawer* sd = [[StringDrawer alloc] initWithFrame:stringRect];
    [self.view addSubview:sd];
    [sd draw:[[NSAttributedString alloc] initWithString:measureText attributes:@{ NSFontAttributeName : targetFont }]];
}

And here is String Drawer (a UIView subclass):

@interface StringDrawer()
@property (nonatomic, copy) NSAttributedString* text;
@end

@implementation StringDrawer

- (instancetype) initWithFrame: (CGRect) frame {
    self = [super initWithFrame:frame];
    self.backgroundColor = [UIColor yellowColor];
    return self;
}

- (void) draw: (NSAttributedString*) text {
    self.text = text;
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
    [self.text drawInRect:rect];
}

@end

Also, if your purpose is to make a label that contains the text by sizing its height, why not let Auto Layout do its thing? This next screen shot is a UILabel, with a width constraint 226, and no height constraint. I've assigned it your font and your text, in code. As you can see, it has sized itself to accommodate all the text:

enter image description here

That was achieved by this code, and no more was needed:

NSString *measureText = @"I'm the king of Portmanteaus ... My students slow clap";    
UIFont *targetFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
self.lab.font = targetFont;
self.lab.text = measureText;