我正在尝试在 SwiftUI 中重新创建 HTML 范围背景着色效果。不应将整个边界视图着色,仅应将文本行着色。这可以在 SwiftUI / UIKit / Core Graphics 中轻松完成吗?
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>
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)
}
}
目前最接近的解决方案似乎是使用 AttributedString 来“破解它”,这不是最优雅的......并且在背景和文本之间添加填充似乎是不可能的。
struct ContentView: View {
let 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."
var attributedString: AttributedString {
var attributedString = AttributedString(text)
attributedString.foregroundColor = .white
attributedString.backgroundColor = .blue
return attributedString
}
var body: some View {
Text(attributedString)
.frame(maxWidth: 300)
.lineSpacing(6)
}
}
真的希望可以使用文本基线作为 CGPath 并添加多个矩形。
在 iOS 18 中,您现在可以创建一个
TextRenderer
来执行此操作。这是一个在文本行后面绘制具有指定填充量的背景的实现。
struct HighlightedTextRenderer: TextRenderer {
let padding: CGFloat
func draw(layout: Text.Layout, in ctx: inout GraphicsContext) {
ctx.translateBy(x: 0, y: padding)
for line in layout {
let rect = line.typographicBounds.rect.insetBy(dx: 0, dy: -padding)
// first draw the rectangle, then draw the text
ctx.fill(Path(rect), with: .color(.yellow))
ctx.draw(line)
}
}
var displayPadding: EdgeInsets {
.init(top: padding, leading: 0, bottom: padding, trailing: 0)
}
func sizeThatFits(proposal: ProposedViewSize, text: TextProxy) -> CGSize {
let textSize = if let proposedHeight = proposal.height {
// if there is a proposed height, propose a smaller height than that to account for padding
text.sizeThatFits(.init(width: proposal.width, height: proposedHeight - padding * 2))
} else {
text.sizeThatFits(proposal)
}
return .init(width: textSize.width, height: textSize.height + padding * 2)
}
}
用途:
Text("Lorem ipsum odor amet, consectetuer adipiscing elit. Vehicula nulla ut porta eros taciti vehicula magna. Nulla ultricies tempor facilisi sem sagittis mollis taciti aliquet cubilia. Litora tortor non semper finibus facilisi elementum litora pellentesque platea. Mauris convallis feugiat luctus donec parturient habitasse torquent pharetra. Lacus maximus massa aliquam facilisi per luctus suscipit in.")
.foregroundStyle(.black)
.font(.title)
.lineSpacing(12)
.textRenderer(HighlightedTextRenderer(padding: 4))
.border(.red)
.padding()