Как получить высоту для NSAttributedString при фиксированной ширине

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

  1. Вызывается - (NSSize) size, но результаты бесполезны (для этой цели), так как они дадут любую ширину, которую желает строка.

  2. Вызов - (void)drawWithRect:(NSRect)rect options:(NSStringDrawingOptions)options с прямоугольником нужной мне ширины и NSStringDrawingUsesLineFragmentOrigin в параметрах, точно так же, как я использую в своем рисунке. Результаты ... трудно понять; конечно не то, что я ищу. (Как указано в ряде мест, в том числе this Тема Cocoa-Dev).

  3. Создание временного NSTextView и выполнение:
    [[tmpView textStorage] setAttributedString:aString];
    [tmpView setHorizontallyResizable:NO];
    [tmpView sizeToFit];

    Когда я запрашиваю фрейм tmpView, ширина все еще такая, какая требуется, а высота часто правильная ... пока я не доберусь до более длинных строк, когда это часто половина размер, который требуется. (Кажется, что максимальный размер не достигнут: один кадр будет иметь высоту 273,0 (примерно на 300 меньше), другой будет 478,0 (только на 60 меньше)).

Буду признателен за любые указатели, если кому-то это удалось.


person bonaldi    schedule 17.02.2010    source источник


Ответы (12)


-[NSAttributedString boundingRectWithSize:options:]

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

В отличие от -[NSAttributedString size], возвращаемый NSRect представляет размеры области, которая изменится, если строка будет нарисована.

Как отмечает @Bryan, _ 5_ не рекомендуется (не рекомендуется) в OS X 10.11 и более поздних версиях. Это связано с тем, что стили строк теперь являются динамическими в зависимости от контекста.

Для OS X 10.11 и более поздних версий см. Расчет Высота текста в документации для разработчиков.

person Graham Miln    schedule 17.03.2010
comment
Кажется, что если вы используете NSStringDrawingUsesDeviceMetrics и нет глифов, это вернет 0. Есть ли способ справиться с обеими ситуациями? Или вы должны каким-то образом знать или отслеживать, добавили ли вы глиф? (Проверено на iOS 8.) - person zekel; 29.10.2014
comment
Имеет смысл, что строка без видимого содержимого вернет прямоугольник нулевого размера. Вы видите новое поведение iOS 8 по сравнению с iOS 7? - person Graham Miln; 29.10.2014
comment
Я имел в виду, что если у вас есть NSAttributedString с текстом (без глифов) и вы используете NSStringDrawingUsesDeviceMetrics, размер будет CGSizeZero. Так что, похоже, вам нужно знать, есть ли глифы, прежде чем вы решите, как получить размер ... - person zekel; 01.11.2014
comment
Возможно ли иметь текст без глифов? Это обычная ситуация? - person Graham Miln; 01.11.2014
comment
Ах, проблема с терминологией. Под глифами я имел в виду NSTextAttachments изображения в NSAttributedString.. Но из вашего ответа я понял, что NSStringDrawingUsesDeviceMetrics можно использовать независимо от NSTextAttachments? - person zekel; 02.11.2014
comment
Да, если вам нужен границы затронутых пикселей. Лучший способ - создать небольшое тестовое приложение и убедиться, что его поведение соответствует вашим потребностям. - person Graham Miln; 02.11.2014
comment
ПРИМЕЧАНИЕ. Вы должны передать параметр NSStringDrawingUsesLineFragmentOrigin, чтобы этот подход возвращал несущественные результаты. См. Мой ответ ниже для получения дополнительной информации. - person Bryan; 15.09.2015
comment
Плохие новости: этот метод устарел, начиная с OS 10.11, И он больше не возвращает правильную высоту. Это слишком низко. Тем не менее, отлично работает на 10.10 и ниже. - person Bryan; 17.10.2015
comment
Это устарело. Актуальный ответ см. В stackoverflow.com/a/54980862/326242. - person Andrew Koster; 25.01.2021

Ответ - использовать
- (void)drawWithRect:(NSRect)rect options:(NSStringDrawingOptions)options
, но rect, который вы передаете, должен иметь 0,0 в измерении, которое вы хотите сделать неограниченным (что, э-э, имеет смысл). Пример: здесь.

person bonaldi    schedule 17.02.2010
comment
Этот ответ устарел. Ответ Грэма, приведенный ниже, является правильным методом по состоянию на 2014 год. - person dwlz; 17.08.2014
comment
Это устарело. Актуальный ответ см. В stackoverflow.com/a/54980862/326242. - person Andrew Koster; 25.01.2021

У меня сложная атрибутированная строка с несколькими шрифтами, и я получил неверные результаты с некоторыми из приведенных выше ответов, которые я попробовал в первую очередь. Использование UITextView дало мне правильную высоту, но было слишком медленно для моего варианта использования (определение размеров ячеек коллекции). Я написал быстрый код, используя тот же общий подход, который описан в Документ Apple, упомянутый ранее и описанный Эриком. Это дало мне правильные результаты с более быстрым выполнением, чем при вычислении UITextView.

private func heightForString(_ str : NSAttributedString, width : CGFloat) -> CGFloat {
    let ts = NSTextStorage(attributedString: str)

    let size = CGSize(width:width, height:CGFloat.greatestFiniteMagnitude)

    let tc = NSTextContainer(size: size)
    tc.lineFragmentPadding = 0.0

    let lm = NSLayoutManager()
    lm.addTextContainer(tc)

    ts.addLayoutManager(lm)
    lm.glyphRange(forBoundingRect: CGRect(origin: .zero, size: size), in: tc)

    let rect = lm.usedRect(for: tc)

    return rect.integral.size.height
}
person Matt R    schedule 29.11.2017
comment
Это решило мою проблему. У меня есть атрибутированная строка с множеством атрибутов, все с разными диапазонами, и типичные API-интерфейсы измерений не совсем работали. Спасибо за этот ответ. - person naturaln0va; 11.06.2020
comment
Это устарело. Актуальный ответ см. В stackoverflow.com/a/54980862/326242. - person Andrew Koster; 25.01.2021

Возможно, вас заинтересует отличная (только для OS X) категория NS(Attributed)String+Geometrics, которая является предназначен для выполнения всевозможных измерений струн, включая то, что вы ищете.

person Rob Keniger    schedule 18.02.2010
comment
Этот код, похоже, специфичен для Mac. Это не поможет с разработкой для iOS. - person Brennan; 24.08.2013
comment
Вы правы, вопрос был о NSTextView, это класс Mac. Я даже не уверен, что у iOS было NSAttributedString, когда я отвечал на этот вопрос. - person Rob Keniger; 24.08.2013
comment
Поздний ввод, но только предупреждение: у меня были проблемы с NS (атрибутированными) String + Geometrics, постоянно недооценивающими высоту, которая требуется для длинных строк. До сих пор я просто обманул результат, добавив дополнительные 25%. Сейчас я ищу новое, лучшее решение. Я не думаю, что NS (Attributed) String + Geometrics хорошо работает в 10.9, 10.10 и 10.11. - person Bryan; 15.09.2015
comment
Это устарело. Актуальный ответ см. В stackoverflow.com/a/54980862/326242. - person Andrew Koster; 25.01.2021

В OS X 10.11+ у меня работает следующий метод (из Apple Расчет высоты текста документ)

- (CGFloat)heightForString:(NSAttributedString *)myString atWidth:(float)myWidth
{
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:myString];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:
        NSMakeSize(myWidth, FLT_MAX)];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    [layoutManager addTextContainer:textContainer];
    [textStorage addLayoutManager:layoutManager];
    [layoutManager glyphRangeForTextContainer:textContainer];
    return [layoutManager
        usedRectForTextContainer:textContainer].size.height;
}
person Erik    schedule 24.11.2015
comment
Это устарело. Актуальный ответ см. В stackoverflow.com/a/54980862/326242. - person Andrew Koster; 25.01.2021
comment
Это единственное, что у меня сработало после множества проблем с boundingRectWithSize: спасибо! - person Noah Nuebling; 15.04.2021
comment
@AndrewKoster, вам следует подумать об удалении ваших комментариев. Это сбило меня с толку и помешало мне попробовать решение, которое в конечном итоге сработало для меня, а не то, которое, по вашему мнению, является актуальным ответом. - person Noah Nuebling; 15.04.2021
comment
... Однако это сработало только тогда, когда я указал для ширины значение, отличное от FLT_MAX. - person Noah Nuebling; 15.04.2021
comment
@NoahNuebling Моя ссылка указывает на ответ @ ZAFAR007. CSS Stack Overflow немного не работает, что прискорбно. Ответ @ ZAFAR007 находится в Swift и использует boundingRect. Это и проще, чем этот ответ, и, судя по моим тестам, работал там, где этот ответ не работал. - person Andrew Koster; 16.04.2021
comment
@AndrewKoster Я использовал атрибутированные строки с полужирным текстом и ссылками (включая подчеркивание). Метод boundingRect, казалось, работал довольно хорошо, но в некоторых крайних случаях не работал. Он немного занизил ширину (IIRC). Я много чего пробовал, чтобы заставить его работать, но у меня ничего не вышло. В комментарии выше даже упоминается, что метод boundingRect устарел в 10.11+. Напротив, решение layourManager прямо из документации Apple, которая, насколько я могу судить, не является устаревшей. Так что я думаю, что у этого решения больше шансов сработать для большинства людей. - person Noah Nuebling; 16.04.2021

Swift 4.2

let attributedString = self.textView.attributedText
let rect = attributedString?.boundingRect(with: CGSize(width: self.textView.frame.width, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
print("attributedString Height = ",rect?.height)
person ZAFAR007    schedule 04.03.2019

Использовать метод NSAttributedString

- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options context:(NSStringDrawingContext *)context

Размер является ограничением для области, вычисляемая ширина области ограничена указанной шириной, тогда как высота может изменяться в зависимости от этой ширины. Можно указать nil для контекста, если он недоступен. Чтобы получить размер многострочного текста, используйте параметры NSStringDrawingUsesLineFragmentOrigin.

person CodeBrew    schedule 14.09.2014
comment
Это устарело. Актуальный ответ см. В stackoverflow.com/a/54980862/326242. - person Andrew Koster; 25.01.2021

Swift 3:

let attributedStringToMeasure = NSAttributedString(string: textView.text, attributes: [
        NSFontAttributeName: UIFont(name: "GothamPro-Light", size: 15)!,
        NSForegroundColorAttributeName: ClickUpConstants.defaultBlackColor
])

let placeholderTextView = UITextView(frame: CGRect(x: 0, y: 0, width: widthOfActualTextView, height: 10))
placeholderTextView.attributedText = attributedStringToMeasure
let size: CGSize = placeholderTextView.sizeThatFits(CGSize(width: widthOfActualTextView, height: CGFloat.greatestFiniteMagnitude))

height = size.height

Этот ответ отлично подходит для меня, в отличие от других, которые давали мне неправильную высоту для больших струн.

Если вы хотите сделать это с обычным текстом вместо текста с атрибутами, сделайте следующее:

let placeholderTextView = UITextView(frame: CGRect(x: 0, y: 0, width: ClickUpConstants.screenWidth - 30.0, height: 10))
placeholderTextView.text = "Some text"
let size: CGSize = placeholderTextView.sizeThatFits(CGSize(width: widthOfActualTextView, height: CGFloat.greatestFiniteMagnitude))

height = size.height
person Josh O'Connor    schedule 10.02.2017
comment
Это устарело. Актуальный ответ см. В stackoverflow.com/a/54980862/326242. - person Andrew Koster; 25.01.2021

Как многие ребята упомянули выше, основываясь на моем тесте.

Я использую open func boundingRect(with size: CGSize, options: NSStringDrawingOptions = [], context: NSStringDrawingContext?) -> CGRect на iOS, как показано ниже:

let rect = attributedTitle.boundingRect(with: CGSize(width:200, height:0), options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil)

Здесь 200 - это фиксированная ширина, как вы ожидали, высота, которую я даю 0, так как я думаю, что лучше сказать, что высота API не ограничена.

Вариант здесь не так важен , Я попробовал .usesLineFragmentOrigin, .usesLineFragmentOrigin.union(.usesFontLeading) или .usesLineFragmentOrigin.union(.usesFontLeading).union(.usesDeviceMetrics), результат тот же.

И результат ожидаемо как будто мой.

Спасибо.

person JerryZhou    schedule 24.07.2017

Я просто потратил на это кучу времени, поэтому даю дополнительный ответ, чтобы спасти других в будущем. Ответ Грэма верен на 90%, но не хватает одного ключевого момента:

Чтобы получить точные результаты с -boundingRectWithSize:options: , вы ДОЛЖНЫ пройти следующие параметры:

NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesDeviceMetrics|NSStringDrawingUsesFontLeading

Если вы опустите строку FragmentOrigin one, вы получите вздор; возвращаемый прямоугольник будет высотой в одну строку и совсем не будет соответствовать размеру, который вы передаете в метод.

Я не понимаю, почему это так сложно и плохо документировано. Но вот оно. Передайте эти параметры, и он будет работать отлично (по крайней мере, на OS X).

person Bryan    schedule 15.09.2015
comment
Также: предполагается, что у вас есть NSAttributedString, созданная с заданным шрифтом, и NSParagraphStyle, который вы ищете - в моем случае это стиль абзаца с выравниванием по левому краю, переносом слов и без переносов. Не забудьте создать это при создании attributedString. - person Bryan; 15.09.2015
comment
Этот метод устарел в 10.11+ и больше не работает правильно; возвращенная высота слишком мала на Эль-Капитане. - person Bryan; 17.10.2015

Ни один ответ на этой странице не помог мне, как и старый старый код Objective-C из документация Apple. В конце концов я начал работать с UITextView, так это сначала установил для него его свойство text или attributedText, а затем вычислил необходимый размер следующим образом:

let size = textView.sizeThatFits(CGSize(width: maxWidth, height: CGFloat.max))

Прекрасно работает. Буйах!

person Patrick Lynch    schedule 21.04.2016

Я нашел вспомогательный класс для определения высоты и ширины attributedText (протестированный код)

https://gist.github.com/azimin/aa1a79aefa1cec031152fa63401d2292

Добавьте указанный выше файл в свой проект

Как использовать

let attribString = AZTextFrameAttributes(attributedString: lbl.attributedText!)
let width : CGFloat = attribString.calculatedTextWidth()
print("width is :: >> \(width)")
person Hardik Thakkar    schedule 25.05.2018
comment
Этот вспомогательный класс выполняет вычисления дважды (высота и ширина). Расчет размера строки стоит недешево, так что это лишние накладные расходы, если вы много занимаетесь версткой. - person Andy Dent; 01.07.2020