IOS:在创建上下文之前如何计算要绘制的文本大小?

8
我需要创建一个包含一行文本的图片。但是问题在于,我首先需要使用CGBitmapContextCreate创建上下文,并需要提供图像的宽度和高度才能后续计算文本区域的边界(通过CTLineGetImageBounds)。但是我想让图片的大小等于文本的边界:( 应该怎么做?
实际上我使用了:
CGBitmapContextCreate
CTLineGetImageBounds
CTLineDraw

也许可以在没有上下文的情况下调用 CTLineGetImageBounds 函数吗?
注意:我使用的是 Delphi,这并不是真正的问题,因为我可以访问所有 API,我只需要函数名称。
6个回答

6

您可以通过以下方式计算所需字体下 NSString 占用的空间:

NSString *testString = @"A test string";
CGSize boundingSize = self.bounds.size;
CGRect labelRect = [testString
                    boundingRectWithSize:boundingSize
                    options:NSStringDrawingUsesLineFragmentOrigin
                    attributes:@{ 
                        NSFontAttributeName : [UIFont systemFontOfSize:14]
                                }
                    context:nil];

当bounding size为您想要的最大尺寸时,您可以使用计算出的尺寸来创建图像。


我不理解,因为我只能访问像CGBitmapContextCreate、CTLineGetImageBounds等API。我不明白你在这里调用的函数是什么?CGRect labelRect = [testString boundingRectWithSize:boundingSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:14] } context:nil]; - zeus
如果您在文件中添加 #import <Foundation/Foundation.h>,则可以访问 NSString 及其 API。 - Ashley
但是我不太明白,这个函数的名称是什么: [testString boundingRectWithSize:boundingSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:14] } context:nil] ... 我使用 Delphi 工作,需要一个 API 函数名 :( - zeus
该方法的名称是 [NSString boundingRectWithSize:options:attributes:](ObjC语法)或 NSString.boundingRectWithSize(_:options:attributes:)(Swift语法)。如果Delphi能够访问所有Cocoa内容,那么它应该能够访问它。您在问题描述中提到的函数是低级C API的一部分,大多数Cocoa开发人员从不使用它们。它们对于这个目的是很好的,但是如果您有困难阅读ObjC方法名称,那么构建任何非常规系统都会带来很多麻烦。许多功能没有C API。 - Rob Napier
不幸的是,看起来我在Delphi中没有boundingRectWithSize :( 也许我只能访问低级C API... - zeus
你是否正在使用“uses Macapi.CoreFoundation;”来导入Core Foundation?如果是的话,请添加“Macapi.Foundation”以获取访问NSString的权限。 - robinkunde

5

这是您需要编写的代码,以获取使用字体时文本的大小。

let widthOfLabel = 400.0
let size = font?.sizeOfString(self.viewCenter.text!, constrainedToWidth: Double(widthOfLabel))

为了获取使用该字体后文本的大小,您需要使用以下字体扩展。

Swift 5:

extension UIFont {
  func sizeOfString(string: String, constrainedToWidth width: CGFloat) -> CGSize {
    return NSString(string: string).boundingRect(
      with: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude),
      options: NSStringDrawingOptions.usesLineFragmentOrigin,
      attributes: [NSAttributedString.Key.font: self],
      context: nil
    ).size
  }
}

之前的Swift 2代码:

extension UIFont {
    func sizeOfString (string: String, constrainedToWidth width: Double) -> CGSize {
        return NSString(string: string).boundingRectWithSize(CGSize(width: width, height: DBL_MAX),
                                                             options: NSStringDrawingOptions.UsesLineFragmentOrigin,
                                                             attributes: [NSFontAttributeName: self],
                                                             context: nil).size
    }
}

3

有几种方法来计算一个更或者更少精确的边界框。您可以使用字体度量,迭代CTRun或使用Cocoa的字符串绘制API进行计算。

然而,我想引起您的注意,这通常是不可能的。虽然上述算法的结果在许多情况下是足够的,但有一些情况下它们是完全错误的:

  • 一些字母的部分超出了字体定义的度量范围。这里是 Zapfino 的一个例子:一个小写"f"的变异。(绿色是文本编辑器的选择)

Zapfino lower case f variation.

  • 有些效果超出了计算框。 轮廓和阴影只是例子。 阴影可以有非常远的距离和巨大的模糊。

enter image description here

  • 您的算法可能无法识别自定义文本属性。

  • 文本可以包含“附件”(例如图片,我认为表情符号也是以附件形式实现的)。这些也必须考虑在内。

当我之前不得不解决这个问题时,我最终得出了以下解决方案:

  1. 创建一个最大尺寸(在本例中为整个页面)的透明位图上下文。
  2. 在所需位置绘制文本。
  3. 从外部到内部遍历支持上下文的字节,扫描每个边缘上的第一个非透明像素。
  4. 使用 CGImageCreateWithImageInRect 剪切结果矩形。

这非常缓慢,但速度对我的用例不是优先考虑的因素。


非常感谢你,Nikolai。是的,这是一种方法,但这将非常慢 :( 而在我的情况下,速度非常重要 :( - zeus

2

好的,我找到了解决方案:使用CTLineGetImageBounds(aline,null{context}); 只需将上下文传递为null即可按预期工作 ;)


嗯,这似乎没有得到文档的支持:“这是必需的,因为上下文中可能有设置,会导致图像边界发生变化。”你有其他参考资料吗? - Nikolai Ruhe

1

看起来你已经完成了大部分工作。你只需要创建一个临时上下文,以调用CTLineGetImageBounds。我认为这个上下文可以非常小(例如一个像素,甚至可能是零),当然你可以重复使用它。理想情况下,它应该具有与最终上下文相同的属性(除了大小),尽管在大多数情况下,即使没有也不会有问题。它仅需要用于元数据。


是的,我在考虑创建一个虚拟上下文,但我不知道创建一个大小为0的虚拟上下文的操作是否会消耗大量CPU周期。因此,想法是创建一个虚拟上下文,计算大小,释放虚拟上下文,创建一个具有计算大小的上下文并在其上绘制。理想情况下,我希望在整个应用程序的生命周期内保留虚拟上下文,但我不确定CTLineGetImageBounds是否适用于多线程:( - zeus

1

以下代码用于根据字体计算NSString的大小

extension UIFont {
func sizeOfString (string: String, constrainedToWidth width: Double) -> CGSize {
    return NSString(string: string).boundingRectWithSize(CGSize(width: width, height: DBL_MAX),
        options: NSStringDrawingOptions.UsesLineFragmentOrigin,
        attributes: [NSFontAttributeName: self],
        context: nil).size
}
}

或者,您可以将其转换为NSString

if let ns_str:NSString = str as NSString? {

let sizeOfString = ns_str.boundingRectWithSize(
                             CGSizeMake(self.titleLabel.frame.size.width, CGFloat.infinity), 
                             options: NSStringDrawingOptions.UsesLineFragmentOrigin, 
                             attributes: [NSFontAttributeName: lbl.font], 
                             context: nil).size
}

我很难过,因为在我的框架中没有“boundingRectWithSize”:( - zeus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接