Swift UI Label实现行间距+每行背景动态宽度方案咨询
实现每行背景自适应文本+行间距的Label效果
嘿,这个需求我之前做过好几次,常规的UILabel和UITextView确实搞不定——它们的背景是整个控件范围,没法单独为每行文本绘制自适应宽度的背景,单纯改TextView的行间距也达不到你要的效果。下面给你两种靠谱的实现方式,亲测在项目里能用:
方案一:自定义UILabel子类(推荐,轻量无冗余)
这种方式借助CoreText解析文本布局,手动绘制每行的背景和文本,完全可控,而且比TextView轻量。
1. 先写自定义Label的代码
import UIKit import CoreText class CustomLineBgLabel: UILabel { // 控制行与行背景之间的空隙(和富文本的lineSpacing对应) var lineGap: CGFloat = 8.0 // 背景的内边距(上下左右的留白,可根据需求调整) var bgInsets: UIEdgeInsets = UIEdgeInsets(top: 4, left: 6, bottom: 4, right: 6) // 每行的背景色 var lineBgColor: UIColor = .white override func drawText(in rect: CGRect) { guard let attrText = attributedText else { super.drawText(in: rect) return } // 用CoreText搭建文本布局 let textStorage = NSTextStorage(attributedString: attrText) let layoutManager = NSLayoutManager() textStorage.addLayoutManager(layoutManager) let textContainer = NSTextContainer(size: bounds.size) textContainer.lineFragmentPadding = 0 // 去掉UIKit默认的左右内边距 textContainer.maximumNumberOfLines = numberOfLines textContainer.lineBreakMode = lineBreakMode layoutManager.addTextContainer(textContainer) // 遍历每一行文本,逐个绘制背景和文字 var glyphRange = CFRangeMake(0, 0) while layoutManager.characterRangeForGlyphRange(glyphRange, actualGlyphRange: nil).location < textStorage.length { // 获取当前行的整体布局矩形(包含行间距) let lineRect = layoutManager.lineFragmentRect(forGlyphAt: glyphRange.location, effectiveRange: &glyphRange) // 获取当前行文本实际占据的矩形(去掉左右空白) let textRect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) // 计算背景的frame:文本矩形加上内边距,再垂直居中对齐 var bgFrame = textRect.inset(by: UIEdgeInsets(top: -bgInsets.top, left: -bgInsets.left, bottom: -bgInsets.bottom, right: -bgInsets.right)) bgFrame.origin.y = lineRect.origin.y + (lineRect.height - textRect.height - bgInsets.top - bgInsets.bottom)/2 // 绘制背景(可以加圆角,这里用4pt示例) lineBgColor.setFill() UIBezierPath(roundedRect: bgFrame, cornerRadius: 4).fill() // 绘制当前行的文本 let textDrawOrigin = CGPoint(x: textRect.origin.x, y: lineRect.origin.y) layoutManager.drawGlyphs(forGlyphRange: glyphRange, at: textDrawOrigin) } } // 重写自适应尺寸,保证AutoLayout能正确计算Label高度 override var intrinsicContentSize: CGSize { guard let attrText = attributedText else { return super.intrinsicContentSize } let textStorage = NSTextStorage(attributedString: attrText) let layoutManager = NSLayoutManager() textStorage.addLayoutManager(layoutManager) let textContainer = NSTextContainer(size: CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)) textContainer.lineFragmentPadding = 0 textContainer.maximumNumberOfLines = numberOfLines textContainer.lineBreakMode = lineBreakMode layoutManager.addTextContainer(textContainer) layoutManager.glyphRange(for: textContainer) let usedRect = layoutManager.usedRect(for: textContainer) // 加上行之间的空隙 let totalHeight = usedRect.height + lineGap * CGFloat(max(0, layoutManager.numberOfLines - 1)) return CGSize(width: bounds.width, height: totalHeight) } }
2. 在Cell里使用这个自定义Label
把你原来的UILabel替换成这个CustomLineBgLabel,然后在配置Cell的时候设置富文本:
// 假设你的Cell里有个叫customLabel的CustomLineBgLabel实例 func configureCell(with content: String) { // 配置富文本的行间距(要和customLabel的lineGap一致) let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineSpacing = customLabel.lineGap let attributedText = NSAttributedString(string: content, attributes: [ .paragraphStyle: paragraphStyle, .font: UIFont.systemFont(ofSize: 16), // 换成你需要的字体 .foregroundColor: UIColor.black // 文本颜色 ]) customLabel.attributedText = attributedText customLabel.lineBgColor = .white // 设置你要的背景色 customLabel.bgInsets = UIEdgeInsets(top: 4, left: 6, bottom: 4, right: 6) // 调整背景内边距 }
方案二:用UITextView配合TextKit(适合需要交互的场景)
如果你的文本需要支持点击、选择等交互,可以用UITextView,通过拦截绘制过程来实现每行背景:
1. 自定义TextView子类
import UIKit class LineBgTextView: UITextView { var lineBgColor: UIColor = .white var bgInsets: UIEdgeInsets = UIEdgeInsets(top: 4, left: 6, bottom: 4, right: 6) override func draw(_ rect: CGRect) { guard let layoutManager = layoutManager, let textContainer = textContainer else { super.draw(rect) return } let textStorage = self.textStorage var glyphRange = CFRangeMake(0, 0) // 遍历每行绘制背景 while layoutManager.characterRangeForGlyphRange(glyphRange, actualGlyphRange: nil).location < textStorage.length { let lineRect = layoutManager.lineFragmentRect(forGlyphAt: glyphRange.location, effectiveRange: &glyphRange) let textRect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) var bgFrame = textRect.inset(by: UIEdgeInsets(top: -bgInsets.top, left: -bgInsets.left, bottom: -bgInsets.bottom, right: -bgInsets.right)) bgFrame.origin.y = lineRect.origin.y + (lineRect.height - textRect.height - bgInsets.top - bgInsets.bottom)/2 lineBgColor.setFill() UIBezierPath(roundedRect: bgFrame, cornerRadius: 4).fill() } // 最后绘制文本 super.draw(rect) } }
2. 使用注意事项
- 设置
isEditable = false、isScrollEnabled = false,让它像Label一样 - 同样需要设置富文本的行间距,保证背景之间的空隙
关键提醒
- 自定义Label的
intrinsicContentSize必须重写,否则AutoLayout会计算错误的高度 - 富文本的
lineSpacing要和自定义控件的lineGap保持一致,这样背景之间的空隙才会正确 - 如果需要圆角,绘制背景的时候调整
cornerRadius即可
内容的提问来源于stack exchange,提问作者Raim Khalil
相关产品推荐
相关产品推荐

