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

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

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

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

26 ответов

Решение

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

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

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

Этот метод кажется ошибочным во многих отношениях. Для одного, как вы заметили, он не учитывает ограничения ширины. С другой стороны, я видел сбой, потому что кажется, что все атрибуты имеют NSObject типа (например, он пытался передать _isDefaultFace к CTFontRef). Также иногда происходит сбой при предоставлении контекста рисования строки, потому что он пытается добавить атрибут с нулевым значением к изменяемой атрибутной строке за кулисами.

Я бы посоветовал вам полностью отказаться от этого метода. Вы можете использовать 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);

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

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

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: не дает точного размера в этом случае.

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

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

Свифт четыре версии

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 работает лучше всего, поскольку обеспечивает целочисленные размеры и хорошо обрабатывает символы эмодзи и другие символы Юникода.

Мне не повезло ни с одним из этих предложений. Моя строка содержала точки пули 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);

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

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

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

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

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

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

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;

@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

Многие из ответов здесь прекрасны, Дэвид Рис красиво резюмирует варианты .

Но иногда, когда есть специальные символы или несколько пробелов, размер всегда был неправильным.

Пример неработающей строки (для меня):

      "hello    .   .  world"

То , что я узнал, что установив керн из NSAttributedString к 1 помогает вернуть нужный размер.

Так:

      NSAttributedString(
    string: "some string",
    attributes: [
        .font: NSFont.preferredFont(forTextStyle: .body), 
        .kern: 1])

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

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

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

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

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

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

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

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

Через несколько часов я обнаружил, что проблема была в lineBreakMode. я имелbyTruncatingTail, но нужно.byWordWrapping(по умолчанию).

Также, когда я пробовал разные способы расчета высоты - нашел два других способа:

Первый

https://gist.github.com/krzyzanowskim/e92eaf31e0419820c0f8cbcf96ba1269#file-stringgetsizethatfits-swift

Второй

      func sizeFittingWidth(_ w: CGFloat) -> CGSize {
    let textStorage = NSTextStorage(attributedString: self)
    let size = CGSize(width: w, height: CGFloat.greatestFiniteMagnitude)
    let boundingRect = CGRect(origin: .zero, size: size)
    let textContainer = NSTextContainer(size: size)
    textContainer.lineFragmentPadding = 0
    let layoutManager = NSLayoutManager()
    layoutManager.addTextContainer(textContainer)
    textStorage.addLayoutManager(layoutManager)
    layoutManager.glyphRange(forBoundingRect: boundingRect, in: textContainer)
    let rect = layoutManager.usedRect(for: textContainer)
    return rect.integral.size
}
    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, который неправильно форматировал. На самом деле установка атрибутов на тексте, наконец, исправила проблему.

У меня возникли проблемы с расчетом высоты. Любой метод, который я пробовал, всегда возвращал слишком маленькие значения.
Для меня проблема оказалась в том, что почему-то attributedStringValueсвойство никогда не содержало никаких атрибутов, которые я установил через Interface Builder. На самом деле он вообще не содержал никаких атрибутов, если бы я не установил атрибутивную строку программно. Даже не шрифт. Вот почему все расчеты высоты были неудачными.

Чтобы заставить его работать, я создал объект, для которого реализована настраиваемая функция для получения правильной атрибутированной строки.

Вот файл реализации для этого Category:

      //
// --------------------------------------------------------------------------
// NSTextField+Additions.m
// Created for Mac Mouse Fix (https://github.com/noah-nuebling/mac-mouse-fix)
// Created by Noah Nuebling in 2021
// Licensed under MIT
// --------------------------------------------------------------------------
//

#import "NSTextField+Additions.h"

@implementation NSTextField (Additions)

// Copy paste template for adding attributes to an attributed string. Contains all possible attributes

//    [str addAttributes:@{
//        NSFontAttributeName:                NSNull.null,
//        NSParagraphStyleAttributeName:      NSNull.null,
//        NSForegroundColorAttributeName:     NSNull.null,
//        NSBackgroundColorAttributeName:     NSNull.null,
//        NSLigatureAttributeName:            NSNull.null,
//        NSKernAttributeName:                NSNull.null,
//        NSStrikethroughStyleAttributeName:  NSNull.null,
//        NSUnderlineStyleAttributeName:      NSNull.null,
//        NSStrokeColorAttributeName:         NSNull.null,
//        NSStrokeWidthAttributeName:         NSNull.null,
//        NSShadowAttributeName:              NSNull.null,
//        NSTextEffectAttributeName:          NSNull.null,
//        NSAttachmentAttributeName:          NSNull.null,
//        NSLinkAttributeName:                NSNull.null,
//        NSBaselineOffsetAttributeName:      NSNull.null,
//        NSUnderlineColorAttributeName:      NSNull.null,
//        NSStrikethroughColorAttributeName:  NSNull.null,
//        NSObliquenessAttributeName:         NSNull.null,
//        NSExpansionAttributeName:           NSNull.null,
//        NSWritingDirectionAttributeName:    NSNull.null,
//        NSVerticalGlyphFormAttributeName:   NSNull.null,
//    } range:NSMakeRange(0, str.length)];

/// In my testing NSTextField.attributedStringValue actually returned a string without _any_ attributes. Not even a font or anything.
/// This lead to issues when trying to calculate the fitting height for a certain width of the NSTextField.
/// This function takes some of the properties of the NSTextField and returns an NSAttributed string based on those.
/// I'm not sure this is perfect, but the returned attributed string describes the way that the text of the NSTextField is rendered close enough to be usable for my height calculations
- (NSAttributedString *)effectiveAttributedStringValue {
    
    NSMutableAttributedString *str = self.attributedStringValue.mutableCopy;

    // Create paragraph style from NSTextField properties
    
    // Not sure if we're setting these properties correctly, and there could be more properties we should be setting
    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
    paragraphStyle.alignment = self.alignment;
    paragraphStyle.baseWritingDirection = self.baseWritingDirection;
    paragraphStyle.lineBreakMode = self.lineBreakMode;
    paragraphStyle.allowsDefaultTighteningForTruncation = self.allowsDefaultTighteningForTruncation;
    if (@available(macOS 10.15, *)) paragraphStyle.lineBreakStrategy = self.lineBreakStrategy;
    
    // Add attributes to AttributedString based on NSTextField properties
     
    [str addAttributes:@{
        NSFontAttributeName:                self.font,
        NSParagraphStyleAttributeName:      paragraphStyle,
        NSForegroundColorAttributeName:     self.textColor,
        NSBackgroundColorAttributeName:     self.backgroundColor,
//        NSLigatureAttributeName:            NSNull.null,
//        NSKernAttributeName:                NSNull.null,
//        NSStrikethroughStyleAttributeName:  NSNull.null,
//        NSUnderlineStyleAttributeName:      NSNull.null,
//        NSStrokeColorAttributeName:         NSNull.null,
//        NSStrokeWidthAttributeName:         NSNull.null,
//        NSShadowAttributeName:              NSNull.null, //self.shadow,
//        NSTextEffectAttributeName:          NSNull.null,
//        NSAttachmentAttributeName:          NSNull.null,
//        NSLinkAttributeName:                NSNull.null,
//        NSBaselineOffsetAttributeName:      NSNull.null, //self.baselineOffsetFromBottom,
//        NSUnderlineColorAttributeName:      NSNull.null,
//        NSStrikethroughColorAttributeName:  NSNull.null,
//        NSObliquenessAttributeName:         NSNull.null,
//        NSExpansionAttributeName:           NSNull.null,
//        NSWritingDirectionAttributeName:    NSNull.null, //self.baseWritingDirection,
//        NSVerticalGlyphFormAttributeName:   NSNull.null,
    } range:NSMakeRange(0, str.length)];
    
    // return NSAttributedString
    
    return str;
    
}

@end


Случайные примечания

  • Некоторые из проблем, которые я читал о людях, UILabel в этой ветке звучат так, как будто они связаны.
  • В конце концов я решил использовать NSTextViewover, потому что его методы получения строки с атрибутами работают из коробки, а использование интерактивных ссылок также было полностью неудачным. У меня сложилось впечатление, что NSTextField это просто беспорядок, которого вам следует избегать за пределами самых простых случаев использования.

Иногда у меня возникали проблемы с вычислением некоторых высот с помощью boundingRect, особенно с абзацами и линиями разрыва. Добавление .usesDeviceMetricsкак параметр сделал свое дело. Теперь, кажется, работает нормально во всех случаях.

      extension NSAttributedString {

    func heightWithWidth(_ width: CGFloat) -> CGFloat {

        let constraints = CGSize(width: width, height: .infinity)

        let bounding = self.boundingRect(with: constraints, options: [.usesLineFragmentOrigin, .usesFontLeading, .usesDeviceMetrics], context: nil)
        return bounding.height

    }
}
   

Итак, я потратил много времени на отладку этого. Я обнаружил, что максимальная высота текста, как определено 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;
}

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

Я использовал 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 не нужны, это не так. Но это решение работало безупречно для любого текста, который я бы добавил.

Обнаружена точно такая же проблема.

Для меня этот вопрос решается TTTAttributedLabel

+ (CGSize)sizeThatFitsAttributedString:(NSAttributedString *)attributedString
                       withConstraints:(CGSize)size
                limitedToNumberOfLines:(NSUInteger)numberOfLines

метод, так как он дает точный результат.

    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, этот метод никогда не сработает! надеюсь, это поможет вам! боже мой удачи.

Другие вопросы по тегам