在 sizeToFit 之后将字形与 UITextView 的顶部对齐
Aligning glyphs to the top of a UITextView after sizeToFit
我正在开发的应用程序支持数百种不同的字体。其中一些字体,尤其是脚本字体,具有明显的上升和下降。当使用其中一些字体在 UITextView
上调用 sizeToFit()
时,我最终得到了明显的顶部和底部填充(左图)。目标是最终得到右侧的图像,使最高的字形与文本视图边界框的顶部齐平。
这是上图的日志:
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:)
没有出现在 return 字形本身的矩形(我猜这总是相同的值,因为它是 return调整线片段的高度?)。
这是解决这个问题最简单的方法吗?如果是,我如何计算文本视图顶部与第一行文本中最高字形顶部之间的距离?
这似乎无法使用 TextKit,但可以直接使用 CoreText。具体来说,CGFont 的 getGlyphBBoxes
return 是 字形 space 单位 中的正确矩形,然后可以将其转换为相对于字体大小的点。
感谢 让我知道 getGlyphBBoxes
以及记录如何将生成的矩形转换为点。
下面是完整的解决方案。这假设您有一个 UITextView
子类,其中预先设置了以下内容:
self.contentInset = .zero
self.textContainerInset = .zero
self.textContainer.lineFragmentPadding = 0.0
此函数现在 return 从文本视图边界顶部到使用的最高字形顶部的距离:
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
以获得所需的结果。
我正在开发的应用程序支持数百种不同的字体。其中一些字体,尤其是脚本字体,具有明显的上升和下降。当使用其中一些字体在 UITextView
上调用 sizeToFit()
时,我最终得到了明显的顶部和底部填充(左图)。目标是最终得到右侧的图像,使最高的字形与文本视图边界框的顶部齐平。
这是上图的日志:
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:)
没有出现在 return 字形本身的矩形(我猜这总是相同的值,因为它是 return调整线片段的高度?)。
这是解决这个问题最简单的方法吗?如果是,我如何计算文本视图顶部与第一行文本中最高字形顶部之间的距离?
这似乎无法使用 TextKit,但可以直接使用 CoreText。具体来说,CGFont 的 getGlyphBBoxes
return 是 字形 space 单位 中的正确矩形,然后可以将其转换为相对于字体大小的点。
感谢 getGlyphBBoxes
以及记录如何将生成的矩形转换为点。
下面是完整的解决方案。这假设您有一个 UITextView
子类,其中预先设置了以下内容:
self.contentInset = .zero
self.textContainerInset = .zero
self.textContainer.lineFragmentPadding = 0.0
此函数现在 return 从文本视图边界顶部到使用的最高字形顶部的距离:
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
以获得所需的结果。