Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSString boundingRectWithSize slightly underestimating the correct width - why?

Tags:

cocoa

I'm creating a class that displays a window with some text, a "Don't show again" checkbox and a button. In order to be reusable, the class resizes the window as needed to fit the text.

However, there is some slight imprecision in the calculations - if I don't add 5 pixels to the width some strings are truncated. (The last word is simply removed.)

Here's what I have:

// NSTextField *textLabel;
// NSString *text;
NSDictionary *stringAttributes = [NSDictionary dictionaryWithObject: [textLabel font] forKey: NSFontAttributeName];
NSRect textFrame = [text boundingRectWithSize:NSMakeSize(textLabel.frame.size.width, (unsigned int)-1)
                                      options:(NSStringDrawingDisableScreenFontSubstitution | NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
                                   attributes:stringAttributes];
textFrame.size.width += 5;

I temporarily set the label's background color to yellow to make the debugging easier, and it clearly expands to almost fit that last word. 4 pixels extra is enough on that test string.
Do note that not all strings fail without these added pixels.

There are two reasons that I care:
1) I want to learn why it's slightly wrong, and more importantly
2) I'm figuring that by changing the width after the calculation, the wrapping can theoretically change and leave the otherwise last line unused, creating extra empty space below the text.

like image 946
exscape Avatar asked Apr 01 '11 10:04

exscape


2 Answers

I've experienced the same issue a few times over the last couple years, running into it whenever I need to use the method. I've never found any information on the matter but what I've gathered over the years is that the issue seems to be that the shorter the string, the more inaccurate the width becomes. After a great enough length, the equation they use is nearly perfect, but before that, it's awful. Why this is, I've never found an explanation for.

In my attempts to work around the issue, I've tried adding flat values and multiplying by constants but those have never given perfect results.

Lately, I've been using

width *= (25/(width+2)+1);

with NSStringDrawingUsesDeviceMetrics. This has given me decent results for lengths I use commonly. I've been seeing a max of 1-2 pixel variance for strings between 8-50 characters, which is accurate enough for my requirements. After 50 characters, boundingRectWithSize:options:attributes: is far more accurate but not perfect. I haven't tested it extensively enough to say much more than that. That said, this should kill any worry of having another line underneath, as the text should never drop to another line. In the higher ranges (haven't tested past 300 characters), it'll slightly overestimate rather than underestimate. In the case that it begins underestimating again, increase the 25 to ~30.

like image 83
Aeorred Avatar answered Nov 11 '22 12:11

Aeorred


Wrap the result with ceilf() so the value is rounded up to the next whole number. That makes a big difference.

like image 1
Brennan Avatar answered Nov 11 '22 10:11

Brennan