iOS 13:如何在 UIKit / SwiftUI 中调整自定义字体的前导/下降/行高
iOS 13: How can I tweak the leading / descend / line height of a custom font in UIKit / SwiftUI
我正在使用自定义字体,不知何故渲染搞砸了行高,可能是因为配置错误 descent
或 leading
(?),所以 g 和 j 在渲染文本的最后一行。我认为这可能是这种特定字体的问题,因为 Sketch 也暴露了相关字体的类似问题,但我觉得我对排版测量或字体的了解还不够。我发现 Typographic Concepts 上的这个 Apple 文档页面非常有见地。
我用 FontLab 的测试版本研究了字体本身,顺便说一句,这是我第一次使用它 - 所以我几乎不知道我在看什么。看起来 g 确实低于配置的 descent
,这似乎是最后一行。 (?)(参见:Character view in FontLab, showing the descend of the g)
通过 lineSpacing
我可以调整线条本身之间的距离来解决前几行中的问题。我知道 iOS 14 将带来一种在 SwiftUI 中修改 Text
的 leading
的方法。但我需要瞄准 iOS 13,所以这没有用。
我也试过 SwiftUI 的 Text
,一个普通的 UILabel.text
和 UILabel.attributedText
自定义的段落样式,但我在那里所做的任何调整似乎都无法缓解这个问题。
视图甚至没有剪裁。仅向框架添加填充根本无济于事。它增加了距离,但 g's 和 j's 仍然被切割。
我能做什么?当最后一行有 g 和 j 时,子类 UILabel
并覆盖 intrinsicContentSize
以添加一些额外的 space?这感觉 a) 脏 b) 鉴于填充没有帮助,它可能无法解决问题?
是字体本身的问题吗?我能以某种方式修补字体而不会使它变得更糟吗?
当我使用较低级别的 API 时,是否有任何方法可以修改字体的前导高度或下降高度?似乎我可以转到 CoreText,因为 CTFontCreateCopyWithAttributes(_:_:_:_:)
是一个候选者,如果我可以通过属性修改前导、行 space 或下降?可以或猴子修补/混合东西而不用射击自己的膝盖吗?我应该只提交 雷达 反馈吗?
您需要使用NSAttributedString
而不是String来控制UILabel的行间距。这是示例代码
let style = NSMutableParagraphStyle()
style.lineSpacing = 20
let string = NSMutableAttributedString(string: "The quick brown fox jumps over the lazy dog, The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog")
string.addAttribute(.paragraphStyle, value: style, range: NSMakeRange(0, string.length))
let label = UILabel(frame: CGRect(x: 20, y: 100, width: 300, height: 500))
label.attributedText = string
label.numberOfLines = 0
self.view.addSubview(label)
输出
我知道你在问什么,因为我在使用自定义字体时遇到过同样的问题。我将提供两种解决方案。在我自己的项目中,我按照您的建议重写 intrinsicContentSize 并为高度和宽度添加填充乘数。在我的例子中,字体是面向用户的,所以我有一个包含所有相关信息的结构。仅供参考 Chalkduster 在系统和剪辑中。我也相信这都是字体文件本身的原因。
解决方案一:
示例:
struct UserFont{
var name : String
var displayName : String
var widthMultiplier : CGFloat
var heightMultiplier : CGFloat
}
然后在我的 UILabel 中,我将其子类化以使用这两个指标
@IBDesignable
class MultiplierUILabel: UILabel {
@IBInspectable var widthPaddingMultiplier : CGFloat = 1
@IBInspectable var heightPaddingMultiplier : CGFloat = 1
override var intrinsicContentSize: CGSize{
return CGSize(width: super.intrinsicContentSize.width * widthPaddingMultiplier, height: super.intrinsicContentSize.height * heightPaddingMultiplier)
}
}
这对我来说是最简单的实现,因为我找到了相应的字体和乘数比例。
解决方案 2:
通过测量字形边界并调整原点 y,您可能能够使绘制发生得稍微高一些。例如,这修复了系统中包含的 Chalkduster 字体的裁剪。
@IBDesignable
class PaddingUILabel: UILabel {
override func drawText(in rect:CGRect) {
//hello
guard let labelText = text else { return super.drawText(in: rect) }
//just some breathing room
let info = boundsForAttrString(str: labelText, font: self.font!, kern: .leastNormalMagnitude)
let glyph = info.glyph
var newRect = rect
let glyphPadding = -(glyph.origin.y)
if glyphPadding - info.descent > 1 && info.descent != 0{
newRect.origin.y -= glyphPadding/2
}else{
if info.descent != 0{
newRect.origin.y += (info.descent - glyphPadding)/2
}
}
super.drawText(in: newRect)
}
func boundsForAttrString(str:String,font:UIFont,kern:CGFloat)->(glyph:CGRect,descent:CGFloat){
let attr = NSAttributedString(string: str, attributes: [.font:font,.kern:kern])
let line = CTLineCreateWithAttributedString(attr)
var ascent : CGFloat = 0
var descent : CGFloat = 0
var leading : CGFloat = 0
CTLineGetTypographicBounds(line, &ascent, &descent, &leading)
let glyph = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds).integral
return (glyph,leading != 0 ? descent : 0)
}
}
解决方案 2 的结果:
系统
使用字形边界的 PaddingUILabel
我正在使用自定义字体,不知何故渲染搞砸了行高,可能是因为配置错误 descent
或 leading
(?),所以 g 和 j 在渲染文本的最后一行。我认为这可能是这种特定字体的问题,因为 Sketch 也暴露了相关字体的类似问题,但我觉得我对排版测量或字体的了解还不够。我发现 Typographic Concepts 上的这个 Apple 文档页面非常有见地。
我用 FontLab 的测试版本研究了字体本身,顺便说一句,这是我第一次使用它 - 所以我几乎不知道我在看什么。看起来 g 确实低于配置的 descent
,这似乎是最后一行。 (?)(参见:Character view in FontLab, showing the descend of the g)
通过 lineSpacing
我可以调整线条本身之间的距离来解决前几行中的问题。我知道 iOS 14 将带来一种在 SwiftUI 中修改 Text
的 leading
的方法。但我需要瞄准 iOS 13,所以这没有用。
我也试过 SwiftUI 的 Text
,一个普通的 UILabel.text
和 UILabel.attributedText
自定义的段落样式,但我在那里所做的任何调整似乎都无法缓解这个问题。
视图甚至没有剪裁。仅向框架添加填充根本无济于事。它增加了距离,但 g's 和 j's 仍然被切割。
我能做什么?当最后一行有 g 和 j 时,子类 UILabel
并覆盖 intrinsicContentSize
以添加一些额外的 space?这感觉 a) 脏 b) 鉴于填充没有帮助,它可能无法解决问题?
是字体本身的问题吗?我能以某种方式修补字体而不会使它变得更糟吗?
当我使用较低级别的 API 时,是否有任何方法可以修改字体的前导高度或下降高度?似乎我可以转到 CoreText,因为 CTFontCreateCopyWithAttributes(_:_:_:_:)
是一个候选者,如果我可以通过属性修改前导、行 space 或下降?可以或猴子修补/混合东西而不用射击自己的膝盖吗?我应该只提交 雷达 反馈吗?
您需要使用NSAttributedString
而不是String来控制UILabel的行间距。这是示例代码
let style = NSMutableParagraphStyle()
style.lineSpacing = 20
let string = NSMutableAttributedString(string: "The quick brown fox jumps over the lazy dog, The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog")
string.addAttribute(.paragraphStyle, value: style, range: NSMakeRange(0, string.length))
let label = UILabel(frame: CGRect(x: 20, y: 100, width: 300, height: 500))
label.attributedText = string
label.numberOfLines = 0
self.view.addSubview(label)
输出
我知道你在问什么,因为我在使用自定义字体时遇到过同样的问题。我将提供两种解决方案。在我自己的项目中,我按照您的建议重写 intrinsicContentSize 并为高度和宽度添加填充乘数。在我的例子中,字体是面向用户的,所以我有一个包含所有相关信息的结构。仅供参考 Chalkduster 在系统和剪辑中。我也相信这都是字体文件本身的原因。
解决方案一: 示例:
struct UserFont{
var name : String
var displayName : String
var widthMultiplier : CGFloat
var heightMultiplier : CGFloat
}
然后在我的 UILabel 中,我将其子类化以使用这两个指标
@IBDesignable
class MultiplierUILabel: UILabel {
@IBInspectable var widthPaddingMultiplier : CGFloat = 1
@IBInspectable var heightPaddingMultiplier : CGFloat = 1
override var intrinsicContentSize: CGSize{
return CGSize(width: super.intrinsicContentSize.width * widthPaddingMultiplier, height: super.intrinsicContentSize.height * heightPaddingMultiplier)
}
}
这对我来说是最简单的实现,因为我找到了相应的字体和乘数比例。
解决方案 2:
通过测量字形边界并调整原点 y,您可能能够使绘制发生得稍微高一些。例如,这修复了系统中包含的 Chalkduster 字体的裁剪。
@IBDesignable
class PaddingUILabel: UILabel {
override func drawText(in rect:CGRect) {
//hello
guard let labelText = text else { return super.drawText(in: rect) }
//just some breathing room
let info = boundsForAttrString(str: labelText, font: self.font!, kern: .leastNormalMagnitude)
let glyph = info.glyph
var newRect = rect
let glyphPadding = -(glyph.origin.y)
if glyphPadding - info.descent > 1 && info.descent != 0{
newRect.origin.y -= glyphPadding/2
}else{
if info.descent != 0{
newRect.origin.y += (info.descent - glyphPadding)/2
}
}
super.drawText(in: newRect)
}
func boundsForAttrString(str:String,font:UIFont,kern:CGFloat)->(glyph:CGRect,descent:CGFloat){
let attr = NSAttributedString(string: str, attributes: [.font:font,.kern:kern])
let line = CTLineCreateWithAttributedString(attr)
var ascent : CGFloat = 0
var descent : CGFloat = 0
var leading : CGFloat = 0
CTLineGetTypographicBounds(line, &ascent, &descent, &leading)
let glyph = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds).integral
return (glyph,leading != 0 ? descent : 0)
}
}
解决方案 2 的结果:
系统
使用字形边界的 PaddingUILabel