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) } }
使用方法
- 把你的UILabel替换成这个
CompactAvenirLabel(名字可以根据你的习惯改) - 设置
customLineHeightMultiple为你需要的紧凑值,比如0.85(根据设计稿微调) - 确保
numberOfLines设为0(让文本自适应行数) - 用Auto Layout约束的话,这个Label会自动计算正确的intrinsic尺寸,完全不需要手动调整Max Height
为什么这个方法有效?
我们手动计算了行高调整后需要额外预留的顶部空间,通过重写textRect告诉系统Label的真实内容尺寸,同时在drawText里把文本绘制到调整后的区域——既保证了紧凑的行间距,又不会裁剪顶部文本,完美复刻Affinity Designer里的效果。
内容的提问来源于stack exchange,提问作者cheaze
相关产品推荐
相关产品推荐

