NSString boundingRectWithSize слегка недооценивает правильную ширину - почему?

Я создаю класс, который отображает окно с некоторым текстом, флажок "Не показывать снова" и кнопку. Чтобы быть повторно используемым, класс изменяет размер окна по мере необходимости, чтобы соответствовать тексту.

Однако в вычислениях есть небольшая неточность - если я не добавляю 5 пикселей в ширину, некоторые строки усекаются. (Последнее слово просто удаляется.)

Вот что у меня есть:

// 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;

Я временно установил цвет фона метки на желтый, чтобы облегчить отладку, и он явно расширяется, чтобы почти соответствовать этому последнему слову. Для этой тестовой строки достаточно 4 пикселов.
Обратите внимание, что не все строки терпят неудачу без этих добавленных пикселей.

Меня беспокоят две причины:
1) Я хочу узнать, почему это немного неправильно, и что еще более важно 2) Я полагаю, что, изменив ширину после вычисления, обертка теоретически может измениться и оставить в противном случае последнюю строку неиспользованной, создавая дополнительное пустое пространство под текстом.

Ответ 1

Я пережил одну и ту же проблему несколько раз за последние пару лет, столкнувшись с ней всякий раз, когда мне нужно использовать этот метод. Я никогда не нашел никакой информации по этому вопросу, но то, что я собрал на протяжении многих лет, состоит в том, что проблема заключается в том, что чем короче строка, тем более неточная ширина становится. После достаточно большой длины уравнение, которое они используют, почти идеально, но до этого оно ужасно. Почему это так, я никогда не нашел объяснений.

В моих попытках обойти проблему я попытался добавить плоские значения и умножить на константы, но они никогда не давали идеальных результатов.

В последнее время я использовал

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

с помощью NSStringDrawingUsesDeviceMetrics. Это дало мне приличные результаты для длин, которые я использую обычно. Я видел максимальную дисперсию в 1-2 пикселя для строк от 8 до 50 символов, что достаточно точно для моих требований. После 50 символов boundingRectWithSize: options: attributes: намного точнее, но не идеально. Я не тестировал его достаточно широко, чтобы сказать гораздо больше. Тем не менее, это должно убить любое беспокойство по поводу того, что под ним должна быть другая строка, поскольку текст никогда не должен опускаться на другую линию. В более высоких диапазонах (не прошедших тестирование более 300 символов), это будет немного переоценивать, а не недооценивать. В случае, если он снова начнет недооценивать, увеличьте значение от 25 до ~ 30.

Ответ 2

Оберните результат с помощью ceilf(), чтобы значение округлилось до следующего целого числа. Это имеет большое значение.

Ответ 3

Я нашел запрос NSTextFieldCell с помощью метода cellSizeForBounds: более точным, чем использование boundingRectWithSize: для определения высоты текста при фиксированной ширине. Вы можете найти, что он работает в общем случае в вашем случае для оценки ширины. (Я использую Mountain Lion, поэтому я не уверен, как это происходит на предыдущих SDK.)

См. мой ответ на этот вопрос относительно оценки высоты текста.