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

如何在SwiftUI/UIKit/Core Graphics中实现HTML span式文本行背景着色?

在SwiftUI中实现HTML Span式文本行背景着色效果

想要在SwiftUI中实现类似HTML <span>的文本行背景着色效果——仅为文本实际占据的行添加背景色,而非给整个容器视图着色。以下是相关示例及解决方案:


HTML实现示例

div {
  max-width: 400px;
  line-height: 2;
  font-family: sans-serif;
}

span {
  background-color: blue;
  color: white;
  padding: 0.3em;
}
<div>
  <span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, nulla eget consequat finibus, tortor erat scelerisque ipsum, nec dictum justo quam in ipsum. Nulla nec eleifend felis. Sed vel semper mauris, a placerat elit.</span>
</div>

现有SwiftUI尝试及问题

以下代码尝试实现需求,但background修饰符会给整个文本容器添加背景,不符合仅文本行着色的要求:

struct ContentView: View {
    var body: some View {
        Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, nulla eget consequat finibus, tortor erat scelerisque ipsum, nec dictum justo quam in ipsum. Nulla nec eleifend felis. Sed vel semper mauris, a placerat elit.")
            .foregroundColor(.white)
            .background(Color.blue)
            .frame(maxWidth: 200)
            .lineSpacing(6)
    }
}

预览效果:
SwiftUI 预览效果


解决方案

方法一:基于UIKit TextKit封装SwiftUI View

借助UIKit的TextKit可以精准控制文本行的背景,封装成SwiftUI可用的View:

import SwiftUI
import UIKit

struct HighlightedText: UIViewRepresentable {
    let text: String
    let textColor: UIColor
    let bgColor: UIColor
    let lineHeight: CGFloat
    let horizontalPadding: CGFloat
    let maxWidth: CGFloat
    
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.isEditable = false
        textView.isScrollEnabled = false
        textView.backgroundColor = .clear
        textView.textContainerInset = .zero
        textView.textContainer.lineFragmentPadding = 0
        
        // 配置段落样式
        let baseFont = UIFont.systemFont(ofSize: UIFont.systemFontSize)
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineSpacing = lineHeight - baseFont.lineHeight
        
        // 配置文本属性
        let attributes: [NSAttributedString.Key: Any] = [
            .foregroundColor: textColor,
            .backgroundColor: bgColor,
            .paragraphStyle: paragraphStyle,
            .font: baseFont,
            .kern: horizontalPadding
        ]
        
        textView.attributedText = NSAttributedString(string: text, attributes: attributes)
        // 计算合适高度
        let fitSize = textView.sizeThatFits(CGSize(width: maxWidth, height: .greatestFiniteMagnitude))
        textView.frame = CGRect(origin: .zero, size: fitSize)
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {}
}

// 使用示例
struct ContentView: View {
    var body: some View {
        HighlightedText(
            text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, nulla eget consequat finibus, tortor erat scelerisque ipsum, nec dictum justo quam in ipsum. Nulla nec eleifend felis. Sed vel semper mauris, a placerat elit.",
            textColor: .white,
            bgColor: .blue,
            lineHeight: 40,
            horizontalPadding: 5,
            maxWidth: 200
        )
    }
}

方法二:Core Graphics手动绘制行背景

通过Core Graphics解析文本布局,手动绘制每行的背景区域:

import SwiftUI

struct LineHighlightedText: View {
    let text: String
    let textColor: Color
    let bgColor: Color
    let lineHeight: CGFloat
    let horizontalPadding: CGFloat
    let maxWidth: CGFloat
    
    var body: some View {
        GeometryReader { outerGeo in
            ZStack(alignment: .topLeading) {
                // 绘制文本背景
                Text(text)
                    .foregroundColor(.clear)
                    .font(.system(size: UIFont.systemFontSize))
                    .lineSpacing(lineHeight - UIFont.systemFont(ofSize: UIFont.systemFontSize).lineHeight)
                    .frame(maxWidth: maxWidth)
                    .background(
                        GeometryReader { textGeo in
                            Path { path in
                                // 用TextKit解析文本布局
                                let layoutManager = NSLayoutManager()
                                let textContainer = NSTextContainer(size: textGeo.size)
                                let textStorage = NSTextStorage(string: text)
                                
                                textStorage.addLayoutManager(layoutManager)
                                layoutManager.addTextContainer(textContainer)
                                textContainer.lineFragmentPadding = 0
                                textContainer.maximumNumberOfLines = 0
                                
                                // 遍历每行文本区域
                                let glyphRange = layoutManager.glyphRange(for: textContainer)
                                layoutManager.enumerateLineFragments(forGlyphRange: glyphRange) { rect, usedRect, _, _, _ in
                                    let adjustedRect = CGRect(
                                        x: usedRect.origin.x - horizontalPadding,
                                        y: usedRect.origin.y,
                                        width: usedRect.width + 2 * horizontalPadding,
                                        height: usedRect.height
                                    )
                                    path.addRect(adjustedRect)
                                }
                            }
                            .fill(bgColor)
                        }
                    )
                
                // 绘制前景文本
                Text(text)
                    .foregroundColor(textColor)
                    .font(.system(size: UIFont.systemFontSize))
                    .lineSpacing(lineHeight - UIFont.systemFont(ofSize: UIFont.systemFontSize).lineHeight)
                    .frame(maxWidth: maxWidth)
                    .padding(.horizontal, horizontalPadding)
            }
        }
        .frame(maxWidth: maxWidth)
    }
}

// 使用示例
struct ContentView: View {
    var body: some View {
        LineHighlightedText(
            text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, nulla eget consequat finibus, tortor erat scelerisque ipsum, nec dictum justo quam in ipsum. Nulla nec eleifend felis. Sed vel semper mauris, a placerat elit.",
            textColor: .white,
            bgColor: .blue,
            lineHeight: 40,
            horizontalPadding: 5,
            maxWidth: 200
        )
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 04:05:21