【问题标题】:Aligning glyphs to the top of a UITextView after sizeToFit在 sizeToFit 之后将字形对齐到 UITextView 的顶部
【发布时间】:2019-03-06 15:45:00
【问题描述】:

我正在开发的应用程序支持数百种不同的字体。其中一些字体,尤其是脚本字体,有明显的上升和下降。当sizeToFit() 使用其中一些字体在UITextView 上调用时,我最终会得到显着的顶部和底部填充(左图)。目标是最终得到右侧的图像,以便最高的字形与文本视图边界框的顶部齐平对齐。

这是上图的日志:

Point Size: 59.0
Ascender:  70.21
Descender:  -33.158
Line Height:  103.368
Leading: 1.416
TextView Height: 105.0

我的第一个想法是查看第一行文本中每个字形的高度,然后计算容器顶部与最高字形顶部之间的偏移量。然后我可以使用textContainerInset 相应地调整上边距。

我在我的UITextView 子类中尝试过这样的事情:

for location in 0 ..< lastGlyphIndexInFirstLine {
    let glphyRect = self.layoutManager.boundingRect(forGlyphRange: NSRange(location: location, length: 1), in: self.textContainer)
    print(glphyRect.size.height) // prints 104.78399999999999 for each glyph
}

不幸的是,这不起作用,因为boundRect(forGlyphRange:in:) 似乎没有返回字形本身的矩形(我猜这总是相同的值,因为它返回的是行片段的高度?)。

这是解决此问题的最简单方法吗?如果是,如何计算文本视图顶部与第一行文本中最高字形顶部之间的距离?

【问题讨论】:

    标签: ios uitextview nsattributedstring core-text textkit


    【解决方案1】:

    这似乎无法使用 TextKit,但可以直接使用 CoreText。具体来说,CGFont 的getGlyphBBoxes字形空间单位返回正确的矩形,然后可以将其转换为相对于字体大小的点。

    感谢this answer 让我知道getGlyphBBoxes 以及记录如何将生成的矩形转换为点。

    以下是完整的解决方案。这假设您有一个 UITextView 子类,并且预先设置了以下内容:

    self.contentInset = .zero
    self.textContainerInset = .zero
    self.textContainer.lineFragmentPadding = 0.0
    

    此函数现在将返回从文本视图边界的顶部到使用的最高字形顶部的距离:

    private var distanceToGlyphs: CGFloat {
        // sanity
        guard
            let font = self.font,
            let fontRef = CGFont(font.fontName as CFString),
            let attributedText = self.attributedText,
            let firstLine = attributedText.string.components(separatedBy: .newlines).first
        else { return 0.0 }
    
        // obtain the first line of text as an attributed string
        let attributedFirstLine = attributedText.attributedSubstring(from: NSRange(location: 0, length: firstLine.count)) as CFAttributedString
    
        // create the line for the first line of attributed text
        let line = CTLineCreateWithAttributedString(attributedFirstLine)
    
        // get the runs within this line (there will typically only be one run when using a single font)
        let glyphRuns = CTLineGetGlyphRuns(line) as NSArray
        guard let runs = glyphRuns as? [CTRun] else { return 0.0 }
    
        // this will store the maximum distance from the baseline
        var maxDistanceFromBaseline: CGFloat = 0.0
    
        // iterate each run
        for run in runs {
            // get the total number of glyphs in this run
            let glyphCount = CTRunGetGlyphCount(run)
    
            // initialize empty arrays of rects and glyphs
            var rects = Array<CGRect>(repeating: .zero, count: glyphCount)
            var glyphs = Array<CGGlyph>(repeating: 0, count: glyphCount)
    
            // obtain the glyphs
            self.layoutManager.getGlyphs(in: NSRange(location: 0, length: glyphCount), glyphs: &glyphs, properties: nil, characterIndexes: nil, bidiLevels: nil)
    
            // obtain the rects per-glyph in "glyph space units", each of which needs to be scaled using units per em and the font size
            fontRef.getGlyphBBoxes(glyphs: &glyphs, count: glyphCount, bboxes: &rects)
    
            // iterate each glyph rect
            for rect in rects {
                // obtain the units per em from the font ref so we can convert the rect
                let unitsPerEm = CGFloat(fontRef.unitsPerEm)
    
                // sanity to prevent divide by zero
                guard unitsPerEm != 0.0 else { continue }
    
                // calculate the actual distance up or down from the glyph's baseline
                let glyphY = (rect.origin.y / unitsPerEm) * font.pointSize
    
                // calculate the actual height of the glyph
                let glyphHeight = (rect.size.height / unitsPerEm) * font.pointSize
    
                // calculate the distance from the baseline to the top of the glyph
                let glyphDistanceFromBaseline = glyphHeight + glyphY
    
                // store the max distance amongst the glyphs
                maxDistanceFromBaseline = max(maxDistanceFromBaseline, glyphDistanceFromBaseline)
            }
        }
    
        // the final top margin, calculated by taking the largest ascender of all the glyphs in the font and subtracting the max calculated distance from the baseline
        return font.ascender - maxDistanceFromBaseline
    }
    

    您现在可以将文本视图的顶部contentInset 设置为-distanceToGlyphs 以达到所需的结果。

    【讨论】:

    • 我应该注意,在我的用例中,每个文本视图只使用一种字体。此解决方案需要稍作调整以支持多种字体。
    猜你喜欢
    • 1970-01-01
    • 2015-08-13
    • 2023-03-13
    • 1970-01-01
    • 2013-12-21
    • 2015-07-31
    • 1970-01-01
    • 1970-01-01
    • 2020-11-16
    相关资源
    最近更新 更多