在 UILabel 的 draw#rect 中获取字形 boundingRect

Posted

技术标签:

【中文标题】在 UILabel 的 draw#rect 中获取字形 boundingRect【英文标题】:Getting a glyph boundingRect in draw#rect in UILabel 【发布时间】:2019-07-08 13:23:05 【问题描述】:

使用 Swift,我想在 UILabel 中的 draw#rect 中获取字形的 boundingRect。

UILabel 已经具有大小(例如在示例中为 300x300)和质量,例如文本居中。

class RNDLabel: UILabel 

    override func draw(_ rect: CGRect) 

        let manager = NSLayoutManager()

        let store = NSTextStorage(attributedString: NSAttributedString(
            string: text!,
            attributes: [NSAttributedString.Key.font: font]))
        store.addLayoutManager(manager)

        let textContainer = NSTextContainer(size: rect.size)
        // note, intrinsicContentSize is identical there, no difference
        manager.addTextContainer(textContainer)

        let glyphRange = manager.glyphRange(
           forCharacterRange: NSRange(location: 0, length: 1),
           actualCharacterRange: nil)
        let glyphRect = manager.boundingRect(
           forGlyphRange: glyphRange, in: textContainer)

        print("glyphRect \(glyphRect)")         
        ...context?.addRect(glyphRect), context?.drawPath(using: .stroke)

        super.draw(rect)
    

绿色方块不正确 - 它应该更像这些红色方块!

似乎有很多问题:

我做的 layoutManager 肯定应该获得 UILabel 的品质,例如“居中文本”? (不过,我相信您实际上不能直接访问 UILabel 的 layoutManager 吗?)

我们应该使用类似 CTLineGetOffsetForStringIndex 的东西吗?这甚至可能在draw#rect中

请注意,除了没有正确的偏移量外,绿色框似乎还是错误的高度。 (它看起来更像是一个普通的旧的 intrinsicContentSize 而不是字形边界框。)

怎么做?

为了记录,我的总体目标是根据实际的字形框(当然对于“y”、“X”等不同)来移动字形。但总的来说,了解字形的框有很多有用的理由。

【问题讨论】:

很简单,TextKit 堆栈与 UILabel 没有直接关系的问题不是吗? UILabel 绘图不是 TextKit 绘图。如果这是一个 UITextView,或者只是一个您使用 TextKit 自己绘制的视图,那么您要求做的事情会更加直接。 是的,但是您将很难找出偏移量是多少。 “你会认为有办法” 不,我不会。也许会这么想。就个人而言,如果我有一个像拖动字形这样的目标,我不会从 UILabel 开始。我是那些喜欢使用框架而不是对抗它的人之一。 基本上,是的。可以使 UITextView 的行为与 UILabel 非常相似(使其不可编辑和不可滚动),但重要的区别是整个文本工具包堆栈直接公开。这就是我要开始的地方。当然,你做什么取决于你。 "他们不会在 UILabel 中公开 layoutManager。"为什么你认为 UILabel 有一个布局管理器?我没有看到有人使用 lldb 或 Hopper。我可能会遗漏一些东西,但我不会特别期望 Apple 会改造 UILabel 以使用 NSLayoutManager。这是可能的,但你有理由期待这是真的吗?反编译drawText(in:),好像是调用textRect(forBounds:),好像是手工布局。我没有看到任何布局管理器。 过去我已经构建了您所描述的几次;我总是用 Core Text 完成布局。如果我今天再写一次,也许我会使用 TextKit。但我不明白你为什么要在 UILabel 上构建它。如果只是出于好奇,那就太好了,但我会花一些时间在 Hopper 中看看 UILabel 是如何在幕后工作的。它主要基于 CoreGraphics 和 NSAttributedString,而不是 CoreText 或 NSLayoutManager。 【参考方案1】:

你最初的问题是一个没有问题的问题,因为它说了两个完全相反的东西。

一方面,你说 UILabel。

另一方面,您使用诸如 NSLayoutManager 和 NSTextStorage 和 NSTextContainer 之类的术语——TextKit 堆栈。

这些是相反的,因为 UILabel 不是使用 TextKit 堆栈绘制的。所以你要求做的就像试图用磁铁举起一张纸;磁铁确实能提升东西,但它们提升的是磁性材料,而纸不是其中之一。可以肯定的是,UILabel 必须以某种确定性的方式进行绘制,但 这种方式是完全未知且不透明的,与 TextKit 无关。

另一方面,UITextView 或您自己使用 TextKit 绘制的视图,确实使用了 TextKit,现在所有的 TextKit 工具都适用。因此,如果这个 UITextView(或您使用 TextKit 自己绘制的视图),您要求做的事情会更加直接。

这不需要对界面的外观有太大的改变。可以使 UITextView 的行为与 UILabel 非常相似(使其不可编辑和不可滚动),但重要的区别是整个文本工具包堆栈直接公开。这就是我要开始的地方。我是那些喜欢使用框架而不是对抗它的人之一。

【讨论】:

【参考方案2】:

原来这个问题的答案似乎是:

事实上,无论是否令人惊讶,您基本上没有访问 UILabel 中的字形信息。

实际上,您无法在 UILabel 中获得那些字形“实际形状”。

特别是 RobN。曾指出,一项调查表明,在 UILabel 中,_drawTextInRect:baselineCalculationOnly 完成了这项工作,这是一大堆临时代码。

情况的总结似乎是 UILabel 只是早于 NSLayoutManager / Core Text 并且(到目前为止)只是简单地不使用那些现代系统。

只有那些现代系统才能给你这个......

... 一种逐个字形的访问方式。

【讨论】:

以上是关于在 UILabel 的 draw#rect 中获取字形 boundingRect的主要内容,如果未能解决你的问题,请参考以下文章

NSString draw(in: rect) 方法自己改变 rect

@IBDesignable UiView draw(_ rect: CGRect) 在 IB 中有效,但在设备上无效

我的 TGraphic.Draw(Canvas, Rect) 代码不起作用

setNeedsDisplay() 没有调用 draw(_ rect: CGRect)

draw(_ rect: CGRect) 不绘制超过一定尺寸

如何从 cellForRowAt 调用 draw(_ rect: CGRect) 在自定义 tableview 上绘图?