boundingRectWithSize для NSAttributedString, возвращающий неправильный размер

151

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

CGRect paragraphRect = [attributedText boundingRectWithSize:CGSizeMake(300,0.0)
  options:NSStringDrawingUsesDeviceMetrics
  context:nil];

Это сломано, или мне нужно сделать что-то еще, чтобы вернуть прямоугольник для обернутого текста?

RunLoop
источник
5
У вас есть стиль абзаца с усечением / отсечением lineBreakMode?
danyowdee
1
если вы читаете это, потому что UILabel измеряет / оборачивает на неправильную ширину, посмотрите на stackoverflow.com/questions/46200027/… . в частности, установка NSAllowsDefaultLineBreakStrategy в false при запуске приложения.
Эрик

Ответы:

312

Похоже, вы не указали правильные параметры. Для упаковки этикеток предоставьте как минимум:

CGRect paragraphRect =
  [attributedText boundingRectWithSize:CGSizeMake(300.f, CGFLOAT_MAX)
  options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
  context:nil];

Примечание: если исходная ширина текста меньше 300.f, переноса строк не будет, поэтому убедитесь, что размер границы правильный, иначе вы все равно получите неправильные результаты.

Эд Макманус
источник
25
Каждый должен смотреть на этот ответ, потому что он работает. Возможно, для этого метода требуется более четкая документация и общие атрибуты; не очевидно, куда вам нужно смотреть.
Macserv
31
Внимание! Не всегда получается! Иногда возвращаемая ширина больше ширины параметра размера. Даже документация по методу Apples гласит: «Ограничения, которые вы указываете в параметре size, являются руководством для средства визуализации размера строки. Однако фактический ограничивающий прямоугольник, возвращаемый этим методом, может быть больше, чем ограничения, если для визуализировать всю строку. "
Клаас
75
API для NSAttributedString и boundingRectWithSize абсолютно шокирует.
Малхал
27
Другой недостаток заключается в том, что высота (и, вероятно, ширина), возвращаемая в paraRect, почти всегда является дробным значением. Так что может получиться сказать, что высота 141,3. Вам нужно использовать результат из ceilf(paragraphRect.size.height)так, чтобы он округлился. Я постоянно об этом забываю и удивляюсь, почему мои лейблы все еще обрезаются.
jamone
39
Я всегда оборачиваю свои boundingRectWithSize:вычисления в CGRectIntegral (), который CGRectIntegral rounds the rectangle’s origin downward and its size upward to the nearest whole integersв данном случае округляет высоту и ширину, чтобы исключить ограничение, если высота или ширина являются дробным значением.
Runmad
47

По какой-то причине 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.

shoan
источник
1
Это работает. Другими способами, которые я пробовал, я никогда не мог добраться до работы. Я думаю, что моя проблема связана со сложностью атрибутивной строки, которую я назначил. Он исходил из RTF-файла и использовал различные шрифты, межстрочный интервал, даже тени, и, несмотря на использование UsesLineFragmentOrigin и UsesFontLeading, я никогда не мог получить достаточно высокий результат, и проблема была тем хуже, чем дольше получала приписанная строка. Я предполагаю, что для относительно простых строк может работать метод boundingRectWithSize . Им действительно нужен лучший метод работы с этим, IMO.
Джон Бушнелл
не думаете ли вы, что создавать UITextViewстолько раз, сколько вызывается heightForRowAtIndexPathметод, излишне? это занимает время
Янош
Я согласен с тобой @ Янош, но это было единственное решение, которое сработало для меня.
Сеан
Кстати, я попросил правильное решение: stackoverflow.com/questions/32495744/…
Янос
это единственное решение, которое работает. Эта проблема имеет 9 лет, и Apple, очевидно, не хотят решать эту проблему, или их инженеры слишком слабы, чтобы найти решение для этого. Мы говорим здесь об iOS 11, все еще несущем ту же ошибку.
Утка
31

Эд МакМанус, безусловно, дал ключ к тому, чтобы заставить это работать. Я нашел случай, который не работает

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

прямоугольник не будет иметь правильную высоту. Обратите внимание, что anotherString (который добавляется к строке ) был инициализирован без словаря атрибутов. Это допустимый инициализатор для anotherString, но boundingRectWithSize: не дает точного размера в этом случае.

Пол Хеллер
источник
33
Спасибо! Оказывается, что КАЖДАЯ часть NSAttributedString должна иметь набор словаря, по крайней мере, с NSFontAttributeName и NSForegroundColorAttributeName, если вы хотите, чтобы boundingRectWithSize действительно работал! Я не вижу этого нигде документально.
Бен Уилер
3
Обратите внимание, что также кажется, что различные NSAttributedStrings, которые объединены, чтобы создать один, все должны использовать ТОЛЬКО ЖЕ словарь (и, следовательно, тот же шрифт), чтобы boundingRectWithSize работал правильно!
Бен Уилер
1
Это решение очень похоже на то, которое я использую для своей категории UILabel для «сжатия в соответствии». Для меня ключом к тому, чтобы заставить это работать, было создание ограничивающего прямоугольника с высотой FLT_MAX. Без этого я бы получил прямоугольник только для одной строки.
dre1138
+1 со всех сторон. Это действительно помогло мне, так что спасибо, ребята.
Wex
У меня была эта проблема. Я использовал пробелы и переводы строк NSMutableAttributedStringбез атрибутов и получал неправильный размер.
вбеженар
28

Мое окончательное решение после долгого исследования:
- 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;

И магия работает - все мои тексты рассчитаны правильно сейчас! Наслаждайтесь!

Юрий Сталоверов
источник
1
да, я тоже заметил, что он не сохраняет ограничения по ширине, он всегда получается больше. Это действительно не круто, они осуждают метод работы, и теперь нам приходится иметь дело с этим дерьмом.
Борис Гафуров
1
Вы, сэр, мой новый герой! Это сводило меня с ума. Я устанавливаю вставки textContainers, поэтому пришлось использовать textView.textContainer.size.width вместо textView.bounds.size.witdh.
Г.К.Бенсон
Я не мог проголосовать достаточно. Так странно, что пробелы не учитываются в ограничивающих вычислениях.
Чейз Холланд
1
Замена пробелов каким-нибудь фиктивным символом, таким как «3», возвращает точную высоту
Абузар Амин
1
это спасло мою карьеру и мои отношения с командой QA. Я должен тебе пиво человек !!
lucaslt89
14

Swift четыре версии

let string = "A great test string."
let font = UIFont.systemFont(ofSize: 14)
let attributes: [NSAttributedStringKey: Any] = [.font: 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 = (string 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 работает лучше всего, поскольку обеспечивает целочисленные размеры и хорошо обрабатывает символы эмодзи и другие символы Юникода.

Дэвид Рис
источник
10

Мне не повезло ни с одним из этих предложений. Моя строка содержала точки пули 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);
Рики
источник
Возможно, это были не точки Unicode, а, возможно, пробелы в конце вашей строки. Смотрите ответ выше: stackoverflow.com/a/25941139/337934 .
СамБ
9

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

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];
Роман Б.
источник
9

Оказывается, что КАЖДАЯ часть NSAttributedString должна иметь набор словаря, по крайней мере, с NSFontAttributeName и NSForegroundColorAttributeName, если вы хотите, чтобы boundingRectWithSize действительно работал!

Я не вижу этого нигде.

Бен Уилер
источник
Спасибо, это было решением для меня, за исключением того, что я обнаружил, что необходим только NSFontAttributeName, а не NSForegroundColorAttributeName
Marmoy
7

@warrenm Извините, что метод фреймсеттера у меня не работает.

Я получил это. Эта функция может помочь нам определить размер кадра, необходимый для диапазона строк NSAttributedString в iphone / Ipad SDK для заданной ширины:

Может использоваться для динамической высоты ячеек 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

Каран Алангат
источник
У меня не работает ни на одном устройстве (iPhone 4 и 5). И на устройстве с iOS7.1.2 и 4s Simulator с iOS8.3 :(
sanjana
Это нужно импортировать?
jose920405
@KaranAlangat да#import <CoreText/CoreText.h>
jose920405
7

Я обнаружил, что предпочтительное решение не обрабатывает разрывы строк.

Я обнаружил, что этот подход работает во всех случаях:

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;
ДМК
источник
В моем случае я должен выполнить вычисления в фоновом потоке, и использование элементов пользовательского интерфейса не допускается
Lubbo
4

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

У меня есть длинная атрибутивная строка, которую я пытался вписать в представление прокрутки, чтобы оно отображалось правильно, не обрезаясь. То, что я сделал, чтобы текст работал надежно, заключался в том, чтобы вообще не устанавливать высоту в качестве ограничения, а вместо этого позволял принимать внутренний размер. Теперь текст отображается правильно, без усечения, и мне не нужно рассчитывать высоту.

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

Бреннан
источник
2
Можете ли вы добавить пример кода, чтобы продемонстрировать это? Спасибо.
Натан Баггиа
3

Я немного опаздываю к игре - но я пытался найти способ, который поможет найти ограничивающий прямоугольник, который поместится вокруг приписанной строки, чтобы создать фокусировочное кольцо, как при редактировании файла в 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];
Крис Уокен
источник
2
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));

Работает на всех шрифтах отлично!

Дмитрий Кулеров
источник
1

У меня была та же проблема, но я понял, что ограниченная высота была установлена ​​правильно. Итак, я сделал следующее:

-(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;
}
завивать волосы щипцами
источник
1
    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, который неправильно форматировал. На самом деле установка атрибутов на тексте, наконец, исправила проблему.

Лил
источник
1

Одна вещь, которую я заметил, состоит в том, что прямоугольник, который должен был вернуться (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;

Для некоторого большего контекста; У меня был многострочный текст, и я пытался найти правильную высоту, чтобы отобразить его. BoundRectWithSize иногда возвращал ширину, большую, чем я бы указал, таким образом, когда я использовал мое прошлое в ширину и вычисленную высоту, чтобы отобразить мой текст, он будет усекать. Из тестирования, когда boundingRectWithSize использовал неправильную ширину, величина, на которую она бы уменьшилась, составляла 1 строку. Поэтому я бы проверил, была ли ширина больше, и если да, то добавил бы lineHeight шрифта, чтобы обеспечить достаточно места, чтобы избежать усечения.

odyth
источник
Как высота линии влияет на ширину?
Рой Мулиа
Высота строки @RoiMulia не влияет на ширину. Я обновил свой ответ, чтобы предоставить больше контекста относительно того, как это исправило мою ошибку.
odyth
0
    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, этот метод никогда не сработает! надеюсь, это поможет вам! удачи

MICHEAL LV
источник
0
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];
Anny
источник
В моем случае я должен выполнить вычисления в фоновом потоке, и использование элементов пользовательского интерфейса не допускается
Lubbo
0

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

Я использовал, UITextViewтак как у него было более хорошее выравнивание текста (выровнять, чего в то время не было в наличии UILabel), но для того, чтобы «симулировать» неинтерактивную и не прокручиваемую UILabel, я бы полностью отключил прокрутку, отскок и взаимодействие с пользователем ,

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

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's 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's 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 );
}

* Выше код не скопирован напрямую из моего источника, я должен был настроить его / очистить его от других вещей, которые не нужны для этой статьи. Не принимайте это за код "вставь и вставь".

Очевидным недостатком является то, что он имеет выделение и освобождение для каждого вызова.

Но, преимущество заключается в том, что вы избежите в зависимости от совместимости между тем, как boundingRectWithSize рисует текст и вычислил его размер и реализация текста чертежа в UITextView(или UILabelкоторые также можно использовать просто заменить UITextViewс UILabel). Таким образом можно избежать любых «ошибок», которые может иметь Apple.

PS Может показаться, что вам не нужен этот «темп», UITextViewи вы можете просто спросить sizeThatFitsнепосредственно у цели, однако это не сработало для меня. Хотя логика сказала бы, что она должна работать, а выделение / выпуск временных UITextViewне нужны, это не так. Но это решение работало безупречно для любого текста, который я бы включил.

Sinisa
источник
В моем случае я должен выполнить вычисления в фоновом потоке, и использование элементов пользовательского интерфейса не допускается
Lubbo
0

Итак, я потратил много времени на отладку этого. Я обнаружил, что максимальная высота текста, определяемая параметром « boundingRectWithSizeпозволено отображать текст», UITextViewбыла меньше, чем размер кадра.

В моем случае размер кадра не более 140pt, но UITextView переносит текст не более 131pt.

Я должен был понять это вручную и жестко закодировать «реальную» максимальную высоту.

Вот мое решение:

- (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;
}
Antzi
источник