这种方法行不通。字符串的文本布局与单个字符的布局有很大不同。您在本文中要解决的问题是字距调整,但您仍然需要处理连字、组合字符和字母形式(尤其是阿拉伯语)。文本在这里是错误的工具。
在 SwiftUI 中你确实无法做到这一点。您需要使用CoreText(CTLine)或TextKit(NSLayoutManager)。
也就是说,这并不能保证与文本完全匹配。我们不知道 Text 会做什么。例如,当出现比预期更小的框架时,它会收紧间距吗?我们不知道,也不能问(如果知道的话,这种方法也无法处理)。但 CoreText 和 TextKit 至少会给您可靠的答案,您可以使用它们自己布局与您生成的指标相匹配的文本。
虽然我不认为这种方法是您想要的方式,但代码本身可以改进。首先,我建议优先选择在 GeometryReader 内部调用 async。
struct WidthKey: PreferenceKey {
static var defaultValue: [CGFloat] = []
static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) {
value.append(contentsOf: nextValue())
}
}
您可以使用以下命令将宽度数据捕获到其中:
extension View {
func captureWidth() -> some View {
background(GeometryReader{ g in
Color.clear.preference(key: WidthKey.self, value: [g.size.width])
})
}
}
稍后将阅读此内容onPreferenceChange
:
.onPreferenceChange(WidthKey.self) { self.widths = $0 }
作为字符串的助手:
extension String {
func runs() -> [String] {
indices.map { String(prefix(through: $0)) }
}
}
有了这些,我们可以编写一个 captureWidths() 函数来捕获所有宽度,但隐藏结果:
func captureWidths(_ string: String) -> some View {
Group {
ForEach(string.runs(), id: \.self) { s in
Text(verbatim: s).captureWidth()
}
}.hidden()
}
请注意,字体尚未设置。这是故意的,它会被称为这样:
captureWidths(string).font(font)
这适用.font
到组,该组将其应用于其中的所有文本。
还要注意使用verbatim
此处(以及稍后创建最终文本时)。默认情况下,传递给 Text 的字符串不是文字。它们是本地化键。这意味着您需要查找正确的本地化值来分解字符。这增加了一些复杂性,我假设你不想要,所以你应该明确地说这个字符串是逐字的(字面意思)。
以及所有一起:
struct WidthKey: PreferenceKey {
static var defaultValue: [CGFloat] = []
static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) {
value.append(contentsOf: nextValue())
}
}
extension View {
func captureWidth() -> some View {
background(GeometryReader{ g in
Color.clear.preference(key: WidthKey.self, value: [g.size.width])
})
}
}
extension String {
func runs() -> [String] {
indices.map { String(prefix(through: $0)) }
}
}
func captureWidths(_ string: String) -> some View {
Group {
ForEach(string.runs(), id: \.self) { s in
Text(s).captureWidth()
}
}.hidden()
}
struct ContentView: View {
@State var widths: [CGFloat] = []
@State var string: String = "WAWE"
let font = Font.system(size: 100)
var body: some View {
ZStack(alignment: .topLeading) {
captureWidths(string).font(font)
Text(verbatim: string).font(font).border(Color.red)
Path { path in
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: 150))
}.stroke(lineWidth: 1)
Text("\(0)").rotationEffect(Angle(degrees: 90), anchor: .bottom)
.position(CGPoint(x: 0, y: 170))
ForEach(widths, id: \.self) { p in
ZStack {
Path { path in
path.move(to: CGPoint(x: p, y: 0))
path.addLine(to: CGPoint(x: p, y: 150))
}.stroke(lineWidth: 1)
Text("\(p)").rotationEffect(Angle(degrees: 90), anchor: .bottom).position(CGPoint(x: p, y: 170))
}
}
}
.padding()
.onPreferenceChange(WidthKey.self) { self.widths = $0 }
}
}
不过,要了解该算法对于并不简单的事情的行为方式:
在从右到左的文本中,这些划分是完全错误的。
注意 T 框太窄了。这是因为在 Zapfino 中,Th 连字比字母 T 加字母 h 宽得多。 (公平地说,Text 几乎无法处理 Zapfino;它几乎总是会剪辑它。但关键是连字可以显着改变布局,并且存在于许多字体中。)