【问题标题】:IOS: how to calculate the size of text to draw BEFORE to create the context?IOS:如何在创建上下文之前计算要绘制的文本大小?
【发布时间】:2016-06-07 23:52:02
【问题描述】:

我需要创建一个包含一行文本的图像。但问题是,我首先需要创建上下文(CGBitmapContextCreate),要求图像的宽度和高度有可能稍后计算文本的边界(通过 CTLineGetImageBounds)。但我想要图像的大小 = 文本的边界 :( 我该怎么办?

其实我用的

CGBitmapContextCreate
CTLineGetImageBounds
CTLineDraw

也许可以在没有上下文的情况下调用 CTLineGetImageBounds ?

注意:我在 delphi 上,这不是问题,因为我可以访问所有 API,我只需要函数名

【问题讨论】:

    标签: ios objective-c swift cgcontext


    【解决方案1】:

    您可以通过执行以下操作根据您要使用的字体计算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];
    

    其中边界尺寸是您希望图像的最大尺寸。然后您可以使用计算出的大小来创建您的图像。

    【讨论】:

    • 不明白,因为我只能访问 CGBitmapContextCreate、CTLineGetImageBounds 等 API。我不明白你在这里调用的函数? CGRect labelRect = [testString boundingRectWithSize:boundingSize options:NSStringDrawingUsesLineFragmentOrigin 属性:@{ NSFontAttributeName : [UIFont systemFontOfSize:14] } context:nil];
    • 如果您将#import 添加到您的文件中,您将可以访问NSString 及其API。
    • 但我不明白,函数名称是这样的:[testString boundingRectWithSize:boundingSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:14] } context:nil] ... i在 Delphi 工作,我需要一个 api 函数名 :(
    • 方法名是[NSString boundingRectWithSize:options:attributes:](ObjC 语法)或NSString.boundingRectWithSize(_:options:attributes:)(Swift 语法)。如果 Delphi 可以访问所有的 Cocoa,那么它应该可以访问它。您在问题中描述的函数是大多数 Cocoa 开发人员从不使用的低级 C API 的一部分。它们可以用于此目的,但如果您在阅读 ObjC 方法名称时遇到问题,那么您将在构建任何不平凡的东西时遇到很多麻烦。许多功能没有 C API。
    • 不幸的是,看起来我在 delphi 中没有 boundingRectWithSize :( 也许我只能访问低级 C API ...
    【解决方案2】:

    这是你需要写的,以便获得带有字体的文本的大小。

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

    您必须使用以下字体扩展名才能使用该字体获取文本的大小。

    斯威夫特 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】:

      有几种方法可以计算或多或少精确的边界框。您可以使用字体度量,迭代CTRuns 或使用可可的字符串绘制API 进行计算。

      然而,我想提请您注意一个问题,即这通常是不可能的。虽然上述算法的结果在很多情况下就足够了,但也有一些情况是完全错误的:

      • 某些字母的部分超出字体中定义的度量标准。这是 Zapfino 的一个例子:小写“f”的变体。 (绿色是文本编辑的选择)

      .

      • 存在超出计算框的效果。轮廓和阴影只是示例。阴影可以有很远的距离和巨大的模糊。

      • 可能存在您的算法不知道的自定义文本属性。

      • 文本可以包含“附件”(就像图像一样,我认为表情符号也可以作为附件实现)。这些也必须考虑在内。

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

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

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

      【讨论】:

      • 非常感谢 Nikolai,是的,这是一种方法,但这会非常慢 :( 就我而言,速度非常重要 :(
      【解决方案4】:

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

      【讨论】:

      • 嗯,这似乎没有得到文档的支持:“这是必需的,因为上下文中可能包含会导致图像边界发生变化的设置。”。您还有其他参考资料吗?
      【解决方案5】:

      下面的代码用于根据字体计算 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”:(
      【解决方案6】:

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

      【讨论】:

      • 是的,我正在考虑创建一个虚拟上下文,但我不知道创建一个虚拟上下文(大小 = 0)的操作是否耗费 CPU 周期。所以这个想法将是创建一个虚拟上下文,计算大小,释放虚拟上下文,创建一个具有计算大小的上下文并在其上绘制。理想情况下,我希望在应用程序的整个生命周期中将虚拟上下文保留在内存中,但我不确定 CTLineGetImageBounds 是否在多线程中工作:(
      猜你喜欢
      • 2011-12-04
      • 2023-03-09
      • 1970-01-01
      • 2023-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-18
      相关资源
      最近更新 更多