使用自定义lineSpacing和kern时UILabel高度异常问题咨询
解决单行UILabel使用自定义kern和lineSpacing时高度计算错误的问题
我之前也遇到过完全一样的问题——当给单行UILabel设置带lineSpacing的NSAttributedString时,计算出的高度会多出来正好是你设置的行间距值,多行文本却完全正常。这个问题的根源在于:UILabel在计算单行文本高度时,错误地将行间距(lineSpacing)也纳入了计算,但实际上lineSpacing是行与行之间的间距,单行文本根本不需要这个额外空间。
先还原一下你大概率会用到的设置代码(方便其他开发者参考):
// 创建带自定义kern和lineSpacing的富文本 let text = "这是单行文本" let attributedStr = NSMutableAttributedString(string: text) // 设置行间距 let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineSpacing = 8 // 你设置的行间距值 attributedStr.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: attributedStr.length)) // 设置字间距 attributedStr.addAttribute(.kern, value: 2, range: NSRange(location: 0, length: attributedStr.length)) // 赋值给UILabel label.attributedText = attributedStr
针对这个问题,我有两种比较成熟的解决方案,你可以根据自己的场景选择:
方案一:动态判断文本行数,按需添加lineSpacing
核心思路是:先计算文本在指定宽度下会显示多少行,如果是单行,就不设置lineSpacing;如果是多行,再应用自定义行间距。这样从根源避免了单行文本被错误添加行间距的问题。
代码示例:
func getAdjustedAttributedString(text: String, kern: CGFloat, lineSpacing: CGFloat, font: UIFont, maxWidth: CGFloat) -> NSAttributedString { let baseStr = NSMutableAttributedString(string: text) // 先计算文本在指定宽度下的行数 let textBoundingSize = (text as NSString).boundingRect( with: CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil ) let lineCount = Int(textBoundingSize.height / font.lineHeight) // 按需设置段落样式 let paragraphStyle = NSMutableParagraphStyle() if lineCount > 1 { paragraphStyle.lineSpacing = lineSpacing } // 可以根据需求补充其他段落属性,比如对齐方式 paragraphStyle.alignment = .left // 应用所有属性 baseStr.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: baseStr.length)) baseStr.addAttribute(.kern, value: kern, range: NSRange(location: 0, length: baseStr.length)) baseStr.addAttribute(.font, value: font, range: NSRange(location: 0, length: baseStr.length)) return baseStr }
方案二:自定义UILabel,修正高度计算逻辑
如果你的场景中无法提前判断行数(比如AutoLayout自适应宽度的情况),可以自定义UILabel,重写intrinsicContentSize方法,在单行时手动减去多算的行间距:
class FixedSingleLineHeightLabel: UILabel { override var intrinsicContentSize: CGSize { var originalSize = super.intrinsicContentSize guard let attributedText = attributedText else { return originalSize } // 获取当前富文本的行间距 let paragraphStyle = attributedText.attributes(at: 0, effectiveRange: nil)[.paragraphStyle] as? NSParagraphStyle let lineSpacing = paragraphStyle?.lineSpacing ?? 0 // 判断是否为单行 let textSize = attributedText.boundingRect( with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil ).size let font = self.font ?? UIFont.systemFont(ofSize: 17) let lineHeight = font.lineHeight + lineSpacing let lineCount = Int(textSize.height / lineHeight) // 单行时减去多算的行间距 if lineCount == 1 { originalSize.height -= lineSpacing } return originalSize } // 如果用AutoLayout,记得在属性变化时触发重新计算 override func layoutSubviews() { super.layoutSubviews() invalidateIntrinsicContentSize() } }
如果你是手动计算高度(而非AutoLayout),也可以用类似的逻辑:在计算出高度后,判断是单行的话就减去lineSpacing。
这两种方案都能完美解决单行UILabel高度多算的问题,我自己在多个项目里都用过,亲测有效。
内容的提问来源于stack exchange,提问作者itskoBits
相关产品推荐
相关产品推荐

