My first idea was based on Text + operator. Seems to be easy, constructing the whole Text by composition /one by one character/ and check the width of partial result ... Unfortunately, I didn't find the way how to do it. All the tricks known to get some geometry (alignmentGuide, GeometryReader, anchorPreferences ...) works as View modifiers! This means the Text + operator is unusable. Simply calculate the position of characters in Text as a sum of Text(String(Character)) widths doesn't work, for example
Text("WAW")
and
HStack(spacing:0) { Text("W"); Text("A"); Text("W") }
width is (as expected) different.
Finally I got (use copy paste to check it) something like
struct ContentView: View {
@State var width: [CGFloat] = []
let font = Font.system(size: 100)
var body: some View {
VStack {
if width.isEmpty {
text(t: Text("W").font(font), width: $width)
text(t: Text("WA").font(font), width: $width)
text(t: Text("WAW").font(font), width: $width)
text(t: Text("WAWE").font(font), width: $width)
} else {
ZStack(alignment: .topLeading) {
Text("WAWE").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(Array(width.sorted(by: <).enumerated()), id: \.0) { p in
ZStack {
Path { path in
path.move(to: CGPoint(x: p.1, y: 0))
path.addLine(to: CGPoint(x: p.1, y: 150))
}.stroke(lineWidth: 1)
Text("\(p.1)").rotationEffect(Angle(degrees: 90), anchor: .bottom).position(CGPoint(x: p.1, y: 170))
}
}
}.padding()
}
}
}
}
func text(t: Text, width: Binding<[CGFloat]>)->some View {
let tt = t.background(
GeometryReader{ proxy->Color in
DispatchQueue.main.async {
width.wrappedValue.append(proxy.size.width)
}
return Color.clear
}
)
return tt.background(Color.yellow)
}
with this result
Which works but is very hacking solution
I am looking for the better way!