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