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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:45:18