You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xcode中UILabel行间距导致文本裁剪是否为Bug?

解决UILabel调整行间距后顶部文本裁剪的问题

嘿,这个坑我之前做Avenir字体的项目时也踩过!默认UILabel处理自定义行高时确实轴得很,尤其是动态自适应文本的场景,顶部裁剪简直让人头大。下面是我摸索出来的完美解决方案,能完美匹配Affinity Designer里的效果:

核心原因

UILabel的默认排版逻辑是基于字体的基线(baseline),当你设置lineHeightMultiple或者调整行高后,它并没有自动计算顶部需要额外预留的空间,导致文本顶部超出Label的bounds被裁剪。而且动态文本自适应尺寸时,intrinsicContentSize也没把这个额外空间算进去。

解决方案:自定义UILabel重写排版逻辑

最靠谱的方式是封装一个自定义Label,手动控制文本的绘制区域和尺寸计算,确保顶部有足够的空间。直接上代码:

import UIKit

class CompactAvenirLabel: UILabel {
    // 自定义行高倍数,比如设为0.8就是紧凑行间距
    var customLineHeightMultiple: CGFloat = 1.0 {
        didSet {
            setNeedsLayout()
            setNeedsDisplay()
        }
    }
    
    override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
        guard let text = text, let font = font else { return super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines) }
        
        // 构建带自定义行高的段落样式
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineHeightMultiple = customLineHeightMultiple
        paragraphStyle.alignment = textAlignment
        
        // 生成富文本计算真实尺寸
        let attributedText = NSAttributedString(string: text, attributes: [
            .font: font,
            .paragraphStyle: paragraphStyle
        ])
        
        let textSize = attributedText.boundingRect(
            with: CGSize(width: bounds.width, height: .greatestFiniteMagnitude),
            options: [.usesLineFragmentOrigin, .usesFontLeading],
            context: nil
        ).size
        
        // 计算需要预留的顶部内边距,避免裁剪
        let fontLineHeight = font.lineHeight
        let adjustedLineHeight = fontLineHeight * customLineHeightMultiple
        let topInset = (adjustedLineHeight - fontLineHeight + font.leading) / 2
        
        // 调整文本矩形,预留顶部空间
        var textRect = CGRect(
            x: bounds.origin.x,
            y: bounds.origin.y + topInset,
            width: ceil(textSize.width),
            height: ceil(textSize.height) - topInset
        )
        
        // 限制行数的情况处理
        if numberOfLines != 0 {
            textRect.size.height = min(textRect.size.height, fontLineHeight * CGFloat(numberOfLines) * customLineHeightMultiple)
        }
        
        return textRect
    }
    
    override func drawText(in rect: CGRect) {
        guard let text = text, let font = font else { super.drawText(in: rect); return }
        
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineHeightMultiple = customLineHeightMultiple
        paragraphStyle.alignment = textAlignment
        
        let attributes: [NSAttributedString.Key: Any] = [
            .font: font,
            .paragraphStyle: paragraphStyle,
            .foregroundColor: textColor ?? .black
        ]
        
        let attributedText = NSAttributedString(string: text, attributes: attributes)
        
        // 计算绘制区域,加上顶部内边距
        let fontLineHeight = font.lineHeight
        let adjustedLineHeight = fontLineHeight * customLineHeightMultiple
        let topInset = (adjustedLineHeight - fontLineHeight + font.leading) / 2
        let drawRect = rect.inset(by: UIEdgeInsets(top: topInset, left: 0, bottom: 0, right: 0))
        
        attributedText.draw(in: drawRect)
    }
}

使用方法

  1. 把你的UILabel替换成这个CompactAvenirLabel(名字可以根据你的习惯改)
  2. 设置customLineHeightMultiple为你需要的紧凑值,比如0.85(根据设计稿微调)
  3. 确保numberOfLines设为0(让文本自适应行数)
  4. 用Auto Layout约束的话,这个Label会自动计算正确的intrinsic尺寸,完全不需要手动调整Max Height

为什么这个方法有效?

我们手动计算了行高调整后需要额外预留的顶部空间,通过重写textRect告诉系统Label的真实内容尺寸,同时在drawText里把文本绘制到调整后的区域——既保证了紧凑的行间距,又不会裁剪顶部文本,完美复刻Affinity Designer里的效果。

内容的提问来源于stack exchange,提问作者cheaze

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:43:55