iOS 13:如何在UIKit / SwiftUI中调整自定义字体的行距/下降/行高?

6
我正在使用自定义字体,但渲染出现了行高问题,可能是由于配置不正确的下降或导向(?),导致渲染文本的最后一行中的g和j被截断。我认为这可能是这种特定字体的问题,因为Sketch也在使用该字体时出现类似的问题,但我感觉自己对排版测量或字体了解得还不够。我发现苹果公司有一篇关于排版概念的文档页面,非常有见地。

Rendered Label exposing the cut off

我使用FontLab的测试版本查看了字体本身,这是我第一次使用该软件,所以我真的不知道自己在看什么。似乎 g 字母会超出配置的 descent,这似乎是最后一行的意思。 (参见:FontLab中的字符视图,显示g的下降

通过lineSpacing,我可以调整行之间的距离,以修复前几行的问题。我知道iOS 14将带来一种方法来修改SwiftUI中Textleading。但我需要针对iOS 13进行开发,所以那并没有帮助。

我还尝试了SwiftUI的

1
有没有可重现的字体链接? - agibson007
@agibson007:这里涉及到的字体是Edmondsans,它是一种专有/商业字体。 - marius
1
我刚刚更新了我的答案,因为我有更多时间测试了系统中包含的大多数字体。我知道这很令人恼火,但我真的认为这是一个字体文件问题,而不是苹果方面的 bug。也许这是一个 bug,谁知道呢。我不认为 UILabel 在幕后使用 Core Text,所以这些函数可能只能证明有限的帮助作用。希望其中一种解决方案有所帮助。 - agibson007
在此向大家反馈我的问题解决情况:最终我联系了设计师,他提供了另一个版本的字体文件,该文件不再出现问题。 - marius
2个回答

3
您需要使用 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)

输出

这是图片描述


谢谢您的回答,我很感激。不幸的是,正如我在问题中所讨论的那样,它并没有帮助解决这个问题。 - marius

1
我知道你的问题,因为我也曾遇到过自定义字体的问题。我将提供两种解决方案。在我的项目中,我采用了你建议的方法,重写intrinsicContentSize并添加高度和宽度的填充倍增器。在我的情况下,字体是面向用户的,所以我有一个结构体来保存所有相关信息。FYI,Chalkduster在系统中且会被裁剪。我也相信这都是由字体文件本身引起的。
解决方案1: 示例:
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的结果: 系统 System

使用字形边界的PaddingUILabel PaddingUILabel


哇,谢谢你详细的回答!使用 @IBDesignable 来检查结果/渲染预览也是个聪明的想法。 - marius
你使用自定义字体的效果如何?只是好奇因为我没有测试过。 - agibson007
1
在所有使用UIViewRepresentable通过属性字符串使用UILabelUITextView的地方,我都使用了类似于解决方案1的解决方法。在某些地方,我已经开始使用CoreText进行测量。但我希望有一种解决方案可以让它与Text一起使用,因为这样更容易处理动态文本和本地化。此外,iOS 13和iOS 14之间有一些细微差别。 - marius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接