如何在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) } }
预览效果:
解决方案
方法一:基于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
相关产品推荐
相关产品推荐

