boundingRectWithSize为NSAttributedString返回错误的大小

Run*_*oop 149 objective-c nsattributedstring

我试图获取一个属性字符串的rect,但是boundingRectWithSize调用不符合我传入的大小并且返回一个具有单行高度而不是大高度的矩形(它是一个长字符串).我已经通过传递一个非常大的高度值和0以及下面的代码进行了实验,但是返回的rect总是相同的.

CGRect paragraphRect = [attributedText boundingRectWithSize:CGSizeMake(300,0.0)
  options:NSStringDrawingUsesDeviceMetrics
  context:nil];
Run Code Online (Sandbox Code Playgroud)

这是否已损坏,或者我是否需要做其他事情以使其返回包裹文本的矩形?

Ed *_*nus 305

看起来你没有提供正确的选项.对于包装标签,至少提供:

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

注意:如果原始文本宽度低于300.f,则不会换行,因此请确保绑定大小正确,否则您仍会得到错误的结果.

  • NSAttributedString和boundingRectWithSize的API绝对令人震惊. (72认同)
  • 我总是在CGRectIntegral()中包装我的`boundingRectWithSize:`计算,它将`CGRectIntegral向下舍入矩形的原点并将其大小向上舍入到最接近的整数整数',在这种情况下,将高度和宽度四舍五入以确保如果高度不会发生削波或宽度小数值. (39认同)
  • 注意!它并不总是有效!有时返回的宽度大于size参数的宽度.甚至Apples方法文档也指出:"您在size参数中指定的约束是渲染器如何调整字符串大小的指南.但是,如果需要额外的空间,此方法返回的实际边界矩形可能大于约束.渲染整个字符串." (30认同)
  • 另一个问题是,paragraphRect中返回的高度(可能是宽度)几乎总是一个小数值.所以可能会说141.3为高度.你需要使用`ceilf(paragraphRect.size.height)`的结果,以便它向上舍入.我一直忘记这一点,并想知道为什么我的标签仍在剪裁. (27认同)
  • 每个人都需要看看这个答案,因为它有效.也许这种方法需要更清晰的文档,并且通常会归因于字符串; 在你需要看的地方并不明显. (23认同)
  • 伙计们,不要忘记Apple声明:__在iOS 7及更高版本中,此方法返回小数大小(在返回的CGRect的大小组件中); 要使用返回的大小来调整视图大小,必须使用ceil函数将其值提升到最接近的更高整数.__ (7认同)
  • 有时这个技巧不起作用.某些字符串对我不起作用. (3认同)
  • 在CGRectIntegral中将其包装起来效果很好.好的电话,@ runmad (2认同)

war*_*enm 118

这种方法在很多方面看起来都很麻烦.首先,正如您所注意到的,它不考虑宽度约束.另一方面,我看到它崩溃了,因为它似乎假设所有属性都是NSObject类型的(例如,它试图传递_isDefaultFace给a 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);
Run Code Online (Sandbox Code Playgroud)

  • 无论我做什么,它仍会返回不正确的大小,即使我不使用Autolayout(如果你将它与表视图一起使用,那将是一场彻底的灾难).我结束了不断重新创建UILabel并使用一个attributesText参数,调用`sizeToFit`,它实际上返回了正确的结果,但前提是第一次设置了attributionText. (8认同)
  • 我结束了必须使用此方法,因为boundingRectWithSize没有为其中带有表情符号字符的attributionString返回正确的高度值. (6认同)
  • 在使用此解决方案之前,我建议您尝试在选项中传递`NSStringDrawingUsesLineFragmentOrigin`,如其他答案所示.以这种方式使用Core Text效率不高,应该被视为最后的手段. (4认同)

sho*_*oan 45

由于某种原因,boundingRectWithSize总是返回错误的大小.我想出了一个解决方案.UItextView -sizeThatFits有一种方法,它返回文本集的正确大小.因此,不使用boundingRectWithSize,而是使用随机帧创建UITextView,并使用相应的宽度和CGFLOAT_MAX高度调用其sizeThatFits.它返回具有适当高度的大小.

   UITextView *view=[[UITextView alloc] initWithFrame:CGRectMake(0, 0, width, 10)];   
   view.text=text;
   CGSize size=[view sizeThatFits:CGSizeMake(width, CGFLOAT_MAX)];
   height=size.height; 
Run Code Online (Sandbox Code Playgroud)

如果你在while循环中计算大小,不要忘记在自动释放池中添加它,因为将创建n个UITextView,如果我们不使用autoreleasepool,应用程序的运行时内存将增加.

  • 这有效。我尝试过的其他方式永远无法工作。我认为我的问题源于我分配的属性字符串的复杂性。它来自一个 RTF 文件并使用了各种字体、行距、甚至阴影,尽管使用了 UsesLineFragmentOrigin 和 UsesFontLeading,但我永远无法得到足够高的结果,而且问题越长,属性字符串越长。我的猜测是,对于相对简单的字符串, boundingRectWithSize 方法 * 可能 * 有效。他们真的需要一种更好的方法来处理这个问题,imo。 (2认同)

Pau*_*ler 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];
Run Code Online (Sandbox Code Playgroud)

rect将没有正确的高度.请注意,anotherString(附加到字符串)是在没有属性字典的情况下初始化的.这是anotherString的合法初始值设定项,boundingRectWithSize:在这种情况下不提供准确的大小.

  • 谢谢!事实证明,如果你希望boundingRectWithSize真正起作用,那么NSAttributedString的每个部分都必须设置一个至少设置了NSFontAttributeName和NSForegroundColorAttributeName的字典!我没有看到任何记录. (29认同)
  • 请注意,为了使boundingRectWithSize正常工作,看起来组合成一个的不同NSAttributedStrings必须全部使用SAME字典(因此使用相同的字体)! (3认同)

小智 27

经过长时间的调查后我的最终决定:
- boundingRectWithSize函数只返回正确的大小,只有不间断的字符序列!如果字符串包含空格或其他东西(由Apple称为"一些字形") - 则无法获得显示文本所需的实际大小!
我用字母替换了字符串中的空格,并立即得到了正确的结果.

Apple在这里说:https: //developer.apple.com/documentation/foundation/nsstring/1524729-boundingrectwithsize

"此方法返回字符串中字形的实际边界.某些字形(例如,空格)允许与传入的大小指定的布局约束重叠,因此在某些情况下,大小组件的宽度值返回的内容CGRect可以超过size参数的宽度值."

所以有必要找到另一种计算实际矩形的方法......


经过长时间的调查,终于找到解决方 我不确定它是否适用于所有相关的案例UITextView,但主要和重要的事情被检测到!

boundingRectWithSizeCTFramesetterSuggestFrameSizeWithConstraints当使用正确的矩形时,函数以及(以及许多其他方法)将计算大小和文本部分的正确性.例如 - UITextViewhas textView.bounds.size.width- 并且此值不是系统在文本绘制时使用的实际矩形UITextView.

我发现非常有趣的参数并在代码中执行简单的计算:

CGFloat padding = textView.textContainer.lineFragmentPadding;  
CGFloat  actualPageWidth = textView.bounds.size.width - padding * 2;
Run Code Online (Sandbox Code Playgroud)

神奇的作品 - 我的所有文本现在计算正确!请享用!


Dav*_*ees 11

斯威夫特四个版本

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
Run Code Online (Sandbox Code Playgroud)

使用CTFramesetter测量文本效果最好,因为它提供了整数大小并且可以很好地处理表情符号和其他unicode字符.


Rom*_* B. 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];
Run Code Online (Sandbox Code Playgroud)


Ric*_*cky 9

我对这些建议都没有好运.我的字符串包含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);
Run Code Online (Sandbox Code Playgroud)


Kar*_*gat 7

@warrenm很抱歉说框架方法对我不起作用.

我得到了这个.这个函数可以帮助我们确定给定宽度的iphone/Ipad SDK中NSAttributedString的字符串范围所需的帧大小:

它可以用于UITableView Cells的动态高度

- (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));
}
Run Code Online (Sandbox Code Playgroud)

感谢HADDAD ISSA >>> http://haddadissa.blogspot.in/2010/09/compute-needed-heigh-for-fixed-width-of.html


小智 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;
Run Code Online (Sandbox Code Playgroud)


Ben*_*ler 7

事实证明,如果你希望boundingRectWithSize真正起作用,那么NSAttributedString的每个部分都必须设置一个至少设置了NSFontAttributeName和NSForegroundColorAttributeName的字典!

我没有看到任何记录.


vau*_*all 5

2022 年 7 月更新

经过更多的尝试和错误并从其他答案获得反馈,特别是那些指出使用NSString.DrawingOptions.usesDeviceMetrics 的答案,我发现这个选项绝对是一个游戏规则改变者,尽管它本身还不够。

使用.deviceMetrics返回正确的height,但在某些情况下它不能正确地适合 aUILabel或 a NSTextField

我能够使其适合所有情况的唯一方法是使用CATextLayer。适用于 iOS 和 macOS。

例子

let attributedString = NSAttributedString(string: "my string")
let maxWidth = CGFloat(300)
let size = attributedString.boundingRect(
                with: .init(width: maxWidth,
                            height: .greatestFiniteMagnitude),
                options: [
                    .usesFontLeading,
                    .usesLineFragmentOrigin,
                    .usesDeviceMetrics])

let textLayer = CATextLayer()
textLayer.frame = .init(origin: .zero, size: size)
textLayer.contentsScale = 2 // for retina
textLayer.isWrapped = true // for multiple lines
textLayer.string = attributedString
Run Code Online (Sandbox Code Playgroud)

然后您可以将 添加CATextLayer到任何NSView/ UIView

苹果系统

let view = NSView()
view.wantsLayer = true
view.layer?.addSublayer(textLayer)
Run Code Online (Sandbox Code Playgroud)

iOS系统

let view = UIView()
view.layer.addSublayer(textLayer)
Run Code Online (Sandbox Code Playgroud)

原始答案 2021 年 2 月

这里的许多答案都很棒,大卫·里斯很好地总结了这些选项

但有时,当存在特殊字符或多个空格时,大小似乎总是错误的。

无效字符串的示例(对我来说):

"hello    .   .  world"
Run Code Online (Sandbox Code Playgroud)

我发现设置紧距有助于返回正确的大小NSAttributedString1

像这样:

NSAttributedString(
    string: "some string",
    attributes: [
        .font: NSFont.preferredFont(forTextStyle: .body), 
        .kern: 1])
Run Code Online (Sandbox Code Playgroud)