BoundingRectWithSize для NSAttributedString, возвращающий неправильный размер
Я пытаюсь получить прямоугольник для атрибутной строки, но вызов boundingRectWithSize не соответствует размеру, который я передаю, и возвращает прямоугольник с одной высотой строки, а не большой высотой (это длинная строка), Я экспериментировал, передавая очень большое значение для высоты, а также 0, как в приведенном ниже коде, но возвращаемый прямой всегда один и тот же.
CGRect paragraphRect = [attributedText boundingRectWithSize:CGSizeMake(300,0.0)
options:NSStringDrawingUsesDeviceMetrics
context:nil];
Является ли это сломанным, или мне нужно сделать что-то еще, чтобы он вернул исправление для обернутого текста?
Ответы
Ответ 1
Похоже, вы не указали правильные варианты. Для обертывания этикеток укажите как минимум:
CGRect paragraphRect =
[attributedText boundingRectWithSize:CGSizeMake(300.f, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
context:nil];
Примечание: если исходная ширина текста меньше 300.f не будет обертывания строки, поэтому убедитесь, что размер привязки верен, иначе вы все равно получите неправильные результаты.
Ответ 2
Этот метод кажется ошибочным по-разному. Во-первых, как вы заметили, он не учитывает ограничения ширины. Для другого, я видел его сбой, потому что кажется, что все атрибуты имеют тип NSObject
(например, он пытался передать _isDefaultFace
в CTFontRef
). Иногда он также возникает при сбое контекста рисования строк, поскольку он пытается добавить атрибут nil-value в изменяемую атрибутную строку за кулисами.
Я бы посоветовал вам полностью избегать этого метода. Вы можете напрямую использовать Core Text, чтобы оценить размер строки, если вы можете справиться с накладными расходами на создание набора фреймов для каждой строки, которую нужно рисовать. Это также не означает ограничения ширины по ширине, но, по моему опыту, он, кажется, попадает в несколько пикселей.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attrString);
CGSize targetSize = CGSizeMake(320, CGFLOAT_MAX);
CGSize fitSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, [attrString length]), NULL, targetSize, NULL);
CFRelease(framesetter);
Ответ 3
По какой-то причине boundingRectWithSize всегда возвращает неправильный размер.
Я понял решение.
Существует метод UItextView -sizeThatFits, который возвращает правильный размер для набора текста.
Поэтому вместо использования boundingRectWithSize создайте UITextView со случайным фреймом и вызовите его sizeThatFits с соответствующей шириной и высотой CGFLOAT_MAX.
Он возвращает размер, который будет иметь соответствующую высоту.
UITextView *view=[[UITextView alloc] initWithFrame:CGRectMake(0, 0, width, 10)];
view.text=text;
CGSize size=[view sizeThatFits:CGSizeMake(width, CGFLOAT_MAX)];
height=size.height;
Если вы вычисляете размер в цикле while, не забудьте добавить это в пул автозапуска, так как будет создано n количество созданных UITextView, память времени выполнения приложения увеличится, если мы не будем использовать autoreleasepool.
Ответ 4
Эд МакМанус, безусловно, обеспечил ключ к тому, чтобы это работало. Я нашел случай, который не работает
UIFont *font = ...
UIColor *color = ...
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName,
color, NSForegroundColorAttributeName,
nil];
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString: someString attributes:attributesDictionary];
[string appendAttributedString: [[NSAttributedString alloc] initWithString: anotherString];
CGRect rect = [string boundingRectWithSize:constraint options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];
rect не будет иметь правильную высоту. Обратите внимание, что anotherString (которая добавлена к строке) была инициализирована без словаря атрибутов. Это законный инициализатор для другойString, но boundingRectWithSize: не дает точного размера в этом случае.
Ответ 5
Мое окончательное решение после долгого расследования:
Функция - boundingRectWithSize
возвращает правильный размер для непрерывной последовательности символов!
В случае, если строка содержит пробелы или что-то еще (называемое Apple "Некоторые из глифов" ) - невозможно получить фактический размер прямоугольника, необходимый для отображения текста!
Я заменил пробелы в своих строках буквами и сразу получил правильный результат.
Apple говорит здесь:
https://developer.apple.com/documentation/foundation/nsstring/1524729-boundingrectwithsize
"Этот метод возвращает фактические границы глифов в строке. Некоторым из глифов (например, пробелов) разрешено перекрывать ограничения макета, заданные размером, переданным в, поэтому в некоторых случаях значение ширины размер возвращаемого значения CGRect
может превышать значение ширины параметра размера."
Итак, необходимо найти другой способ вычисления фактического прямоугольника...
После долгого решения процесса исследования, наконец, найдено!!!
Я не уверен, что он будет работать хорошо для всех случаев, связанных с UITextView
, но главное и важное было обнаружено!
boundingRectWithSize
, а также CTFramesetterSuggestFrameSizeWithConstraints
(и многие другие методы) будут вычислять размер и часть текста правильно, если используется правильный прямоугольник.
Например, UITextView
имеет textView.bounds.size.width
- и это значение не является фактическим прямоугольником, используемым системой при рисовании текста на UITextView
.
Я нашел очень интересный параметр и выполнил простой расчет в коде:
CGFloat padding = textView.textContainer.lineFragmentPadding;
CGFloat actualPageWidth = textView.bounds.size.width - padding * 2;
И волшебные работы - все мои тексты рассчитаны правильно сейчас!
Наслаждайтесь!
Ответ 6
Если вы хотите получить ограничивающий прямоугольник, обрезая хвост, этот вопрос может помочь вам.
CGFloat maxTitleWidth = 200;
NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.lineBreakMode = NSLineBreakByTruncatingTail;
NSDictionary *attributes = @{NSFontAttributeName : self.textLabel.font,
NSParagraphStyleAttributeName: paragraph};
CGRect box = [self.textLabel.text
boundingRectWithSize:CGSizeMake(maxTitleWidth, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:attributes context:nil];
Ответ 7
@warrenm Извините, что метод framesetter не работал у меня.
Я получил это. Эта функция может помочь нам определить размер кадра, необходимый для строкового диапазона NSAttributedString в SDK iphone/Ipad для заданной ширины:
Он может использоваться для динамической высоты ячеек UITableView
- (CGSize)frameSizeForAttributedString:(NSAttributedString *)attributedString
{
CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
CGFloat width = YOUR_FIXED_WIDTH;
CFIndex offset = 0, length;
CGFloat y = 0;
do {
length = CTTypesetterSuggestLineBreak(typesetter, offset, width);
CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(offset, length));
CGFloat ascent, descent, leading;
CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
CFRelease(line);
offset += length;
y += ascent + descent + leading;
} while (offset < [attributedString length]);
CFRelease(typesetter);
return CGSizeMake(width, ceil(y));
}
Благодаря HADDAD ISSA → > http://haddadissa.blogspot.in/2010/09/compute-needed-heigh-for-fixed-width-of.html
Ответ 8
Мне не повезло ни с одним из этих предложений. Моя строка содержала маркеры unicode, и я подозреваю, что они вызывали горе в расчете. Я заметил, что UITextView обрабатывал чертеж, поэтому я посмотрел на это, чтобы использовать его вычисления. Я сделал следующее, что, вероятно, не так оптимально, как методы рисования NSString, но, по крайней мере, это точно. Это также немного более оптимально, чем инициализация UITextView только для вызова -sizeThatFits:
.
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(width, CGFLOAT_MAX)];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[layoutManager addTextContainer:textContainer];
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:formattedString];
[textStorage addLayoutManager:layoutManager];
const CGFloat formattedStringHeight = ceilf([layoutManager usedRectForTextContainer:textContainer].size.height);
Ответ 9
У меня была такая же проблема, когда я не получал точный размер, используя эти методы, и я изменил свой подход, чтобы заставить его работать.
У меня длинная атрибутная строка, которую я пытался вставить в прокрутку, чтобы она отображалась правильно, без усечения. То, что я сделал, чтобы сделать работу над текстом надежно, заключалось в том, чтобы не устанавливать высоту вообще как ограничение и вместо этого допускать, чтобы внутренний размер захватывал. Теперь текст отображается правильно, без усечения, и мне не нужно вычислять высоту.
Я полагаю, что если мне действительно нужно получить высоту надёжно, я бы создал скрытое представление и эти ограничения и получить высоту кадра после применения ограничений.
Ответ 10
Я обнаружил, что предпочтительное решение не обрабатывает разрывы строк.
Я нашел, что этот подход работает во всех случаях:
UILabel* dummyLabel = [UILabel new];
[dummyLabel setFrame:CGRectMake(0, 0, desiredWidth, CGFLOAT_MAX)];
dummyLabel.numberOfLines = 0;
[dummyLabel setLineBreakMode:NSLineBreakByWordWrapping];
dummyLabel.attributedText = myString;
[dummyLabel sizeToFit];
CGSize requiredSize = dummyLabel.frame.size;
Ответ 11
Я немного опаздываю до игры - но я пытался выяснить способ, который работает, чтобы найти ограничивающий прямоугольник, который поместится вокруг атрибутивной строки, чтобы сделать кольцо фокусировки, как редактирование файла в Finder. все, что я пробовал, провалилось, когда в конце строки или нескольких пробелов внутри строки есть пробелы. boundingRectWithSize
терпит неудачу для этого, а также CTFramesetterCreateWithAttributedString
.
Используя NSLayoutManager
, следующий код, похоже, делает трюк во всех случаях, которые я нашел до сих пор, и возвращает прямоугольник, который отлично ограничивает строку. Бонус: если вы выберете текст, края выделения перейдут к границам возвращенного прямоугольника. В приведенном ниже коде используется layoutManager из NSTextView
.
NSLayoutManager* layout = [self layoutManager];
NSTextContainer* container = [self textContainer];
CGRect focusRingFrame = [layout boundingRectForGlyphRange:NSMakeRange(0, [[self textStorage] length]) inTextContainer:container];
Ответ 12
Быстрая трех версия
let string = "A great test string."
let font = UIFont.systemFont(ofSize: 14)
let attributes: [String: Any] = [NSFontAttributeName: font]
let attributedString = NSAttributedString(string: string, attributes: attributes)
let largestSize = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)
//Option one (best option)
let framesetter = CTFramesetterCreateWithAttributedString(attributedString)
let textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(), nil, largestSize, nil)
//Option two
let textSize = (alert.alertDescription as NSString).boundingRect(with: largestSize, options: [.usesLineFragmentOrigin , .usesFontLeading], attributes: attributes, context: nil).size
//Option three
let textSize = attributedString.boundingRect(with: largestSize, options: [.usesLineFragmentOrigin , .usesFontLeading], context: nil).size
Измерение текста с помощью CTFramesetter работает лучше всего, поскольку оно обеспечивает целочисленные размеры и хорошо обрабатывает emoji и другие символы Unicode.
Ответ 13
У меня была та же проблема, но я понял, что ограничение по высоте установлено правильно. Поэтому я сделал следующее:
-(CGSize)MaxHeighForTextInRow:(NSString *)RowText width:(float)UITextviewWidth {
CGSize constrainedSize = CGSizeMake(UITextviewWidth, CGFLOAT_MAX);
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:@"HelveticaNeue" size:11.0], NSFontAttributeName,
nil];
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:RowText attributes:attributesDictionary];
CGRect requiredHeight = [string boundingRectWithSize:constrainedSize options:NSStringDrawingUsesLineFragmentOrigin context:nil];
if (requiredHeight.size.width > UITextviewWidth) {
requiredHeight = CGRectMake(0, 0, UITextviewWidth, requiredHeight.size.height);
}
return requiredHeight.size;
}
Ответ 14
textView.textContainerInset = UIEdgeInsetsZero;
NSString *string = @"Some string";
NSDictionary *attributes = @{NSFontAttributeName:[UIFont systemFontOfSize:12.0f], NSForegroundColorAttributeName:[UIColor blackColor]};
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:attributes];
[textView setAttributedText:attributedString];
CGRect textViewFrame = [textView.attributedText boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.view.frame)-8.0f, 9999.0f) options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];
NSLog(@"%f", ceilf(textViewFrame.size.height));
Отлично работает на всех шрифтах!
Ответ 15
NSDictionary *stringAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
[UIFont systemFontOfSize:18], NSFontAttributeName,
[UIColor blackColor], NSForegroundColorAttributeName,
nil];
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:myLabel.text attributes:stringAttributes];
myLabel.attributedText = attributedString; //this is the key!
CGSize maximumLabelSize = CGSizeMake (screenRect.size.width - 40, CGFLOAT_MAX);
CGRect newRect = [myLabel.text boundingRectWithSize:maximumLabelSize
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:stringAttributes context:nil];
self.myLabelHeightConstraint.constant = ceilf(newRect.size.height);
Я пробовал все на этой странице и все еще имел один случай для UILabel, который не форматировался правильно. Фактически установка атрибутаText на этикетке окончательно устранила проблему.
Ответ 16
Одна вещь, которую я заметил, это то, что прямоугольник, возвращающийся с (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context
, имел бы большую ширину, чем то, что я передал. Когда это произошло, моя строка будет усечена. Я решил это следующим образом:
NSString *aLongString = ...
NSInteger width = //some width;
UIFont *font = //your font;
CGRect rect = [aLongString boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
options:(NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin)
attributes:@{ NSFontAttributeName : font,
NSForegroundColorAttributeName : [UIColor whiteColor]}
context:nil];
if(rect.size.width > width)
{
return rect.size.height + font.lineHeight;
}
return rect.size.height;
Ответ 17
Оказывается, что каждая часть NSAttributedString должна иметь набор словарей с по меньшей мере NSFontAttributeName и NSForegroundColorAttributeName, если вы хотите, чтобы функция boundingRectWithSize действительно работала!
Я не вижу документально нигде.
Ответ 18
NSAttributedString *attributedText =[[[NSAttributedString alloc]
initWithString:joyMeComment.content
attributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:TextFont]}] autorelease];
CGRect paragraphRect =
[attributedText boundingRectWithSize:CGSizeMake(kWith, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
context:nil];
contentSize = paragraphRect.size;
contentSize.size.height+=10;
label.frame=contentSize;
Если рамка метки не добавляет 10, этот метод никогда не будет работать! надеюсь, это поможет вам! goog удачи.
Ответ 19
Я хотел бы добавить свои мысли, так как у меня была такая же проблема.
Я использовал UITextView
, поскольку у него было более приятное выравнивание текста (оправдание, которое в то время не было доступно в UILabel
), но для того, чтобы "имитировать" неинтерактивный, не прокручиваемый UILabel
, я 'd полностью отключить прокрутку, подпрыгивание и взаимодействие с пользователем.
Конечно, проблема заключалась в том, что текст был динамическим, и, хотя ширина была фиксированной, высота должна быть пересчитана каждый раз, когда я устанавливаю новое текстовое значение.
boundingRectWithSize
не работал у меня вообще, из того, что я мог видеть, UITextView
добавлял некоторый запас сверху, который boundingRectWithSize
не попадал в счет, следовательно, высота, полученная из boundingRectWithSize
было меньше, чем должно быть.
Поскольку текст не обновлялся быстро, он просто использовался для некоторой информации, которая может обновляться каждые 2-3 секунды, я решил следующий подход:
/* This f is nested in a custom UIView-inherited class that is built using xib file */
-(void) setTextAndAutoSize:(NSString*)text inTextView:(UITextView*)tv
{
CGFloat msgWidth = tv.frame.size.width; // get target width
// Make "test" UITextView to calculate correct size
UITextView *temp = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, msgWidth, 300)]; // we set some height, really doesn't matter, just put some value like this one.
// Set all font and text related parameters to be exact as the ones in targeted text view
[temp setFont:tv.font];
[temp setTextAlignment:tv.textAlignment];
[temp setTextColor:tv.textColor];
[temp setText:text];
// Ask for size that fits :P
CGSize tv_size = [temp sizeThatFits:CGSizeMake(msgWidth, 300)];
// kill this "test" UITextView, it purpose is over
[temp release];
temp = nil;
// apply calculated size. if calcualted width differs, I choose to ignore it anyway and use only height because I want to have width absolutely fixed to designed value
tv.frame = CGRectMake(tv.frame.origin.x, tv.frame.origin.y, msgWidth, tv_size.height );
}
* Выше код напрямую не копируется из моего источника, мне пришлось его отрегулировать/очистить от множества других вещей, которые не нужны для этой статьи. Не принимайте его для копирования-вставки и-it-will-work-code.
Очевидным недостатком является то, что он выделяет и освобождает для каждого вызова.
Но преимущество заключается в том, что вы избегаете зависимости от совместимости между тем, как boundingRectWithSize рисует текст и вычисляет его размер и реализует текстовый чертеж в UITextView
(или UILabel
, который также можно использовать только для замены UITextView
с помощью UILabel
). Любые "ошибки", которые Apple может иметь, избегают.
P.S. Казалось бы, вам не нужен этот "temp" UITextView
и он может просто запросить sizeThatFits
непосредственно из цели, однако это не сработало для меня. Хотя логика скажет, что она должна работать, а выделение/освобождение временного UITextView
не нужны, это не так. Но это решение работало безупречно для любого текста, который я бы установил.
Ответ 20
Add Following methods in ur code for getting correct size of attribute string
1.
- (CGFloat)findHeightForText:(NSAttributedString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font
{
UITextView *textView = [[UITextView alloc] init];
[textView setAttributedText:text];
[textView setFont:font];
CGSize size = [textView sizeThatFits:CGSizeMake(widthValue, FLT_MAX)];
return size.height;
}
2. Call on heightForRowAtIndexPath method
int h = [self findHeightForText:attrString havingWidth:yourScreenWidth andFont:urFont];
Ответ 21
Хорошо, поэтому я потратил много времени на отладку. Я узнал, что максимальная высота текста, определенная boundingRectWithSize
, разрешенная для отображения текста моим UITextView
, была ниже, чем размер кадра.
В моем случае фрейм не более 140pt, но UITextView допускает текст не более 131pt.
Мне приходилось вычислять это вручную и hardcode "реальную" максимальную высоту.
Вот мое решение:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
NSString *proposedText = [textView.text stringByReplacingCharactersInRange:range withString:text];
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:proposedText];
CGRect boundingRect;
CGFloat maxFontSize = 100;
CGFloat minFontSize = 30;
CGFloat fontSize = maxFontSize + 1;
BOOL fit;
NSLog(@"Trying text: \"%@\"", proposedText);
do {
fontSize -= 1;
//XXX Seems like trailing whitespaces count for 0. find a workaround
[attributedText addAttribute:NSFontAttributeName value:[textView.font fontWithSize:fontSize] range:NSMakeRange(0, attributedText.length)];
CGFloat padding = textView.textContainer.lineFragmentPadding;
CGSize boundingSize = CGSizeMake(textView.frame.size.width - padding * 2, CGFLOAT_MAX);
boundingRect = [attributedText boundingRectWithSize:boundingSize options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil];
NSLog(@"bounding rect for font %f is %@; (max is %f %f). Padding: %f", fontSize, NSStringFromCGRect(boundingRect), textView.frame.size.width, 148.0, padding);
fit = boundingRect.size.height <= 131;
} while (!fit && fontSize > minFontSize);
if (fit) {
self.textView.font = [self.textView.font fontWithSize:fontSize];
NSLog(@"Fit!");
} else {
NSLog(@"No fit");
}
return fit;
}