Why does a custom coordinate space (e.g., "White Screen") in SwiftUI not use .zero as its origin when measured via GeometryReader? Instead, it appears to inherit its position from the global or parent coordinate space. How can I ensure that the custom coordinate space defines its origin at .zero and behaves as the base frame for measurements?
Currently, I am brute-forcing the origin to get what I need, but if this is the only way to set my coordinate space to zero at the top-left, what is the use case for it? I mean, it’s just inheriting from a parent. So, if I’m going to introduce my custom coordinate space to my app, I want all UI elements to inherit from it and for it to become my new custom global system.
P.S. The function that returns the string does not update the view when the state value changes. I'm not sure why; you have to resize the window to see the updated string in the text.
import SwiftUI
struct ContentView: View {
private let coordinateSpaceName: String = "White Screen"
@State private var screenCGRect: CGRect = CGRect()
@State private var bruteForce: Bool = Bool()
var body: some View {
VStack(spacing: 10.0) {
Text(CGRectdescription(id: coordinateSpaceName, rect: screenCGRect)).font(.title3)
GeometryReader { geometryValue in
ZStack {
Color.white.opacity(0.5).border(Color.black)
.coordinateSpace(name: coordinateSpaceName)
Color.red
.frame(width: 1.0, height: screenCGRect.size.height)
Color.red
.frame(width: screenCGRect.size.width, height: 1.0)
}
.onAppear {
if (bruteForce) {
screenCGRect = CGRect(origin: .zero, size: geometryValue.size)
}
else {
screenCGRect = geometryValue.frame(in: .named(coordinateSpaceName))
}
}
.onChange(of: geometryValue.frame(in: .named(coordinateSpaceName))) { newValue in
if (bruteForce) {
screenCGRect = CGRect(origin: .zero, size: newValue.size)
}
else {
screenCGRect = newValue
}
}
}
Button(bruteForce ? "Geometry Reader Origin" : "Brute Force Origin") {
bruteForce.toggle()
}
}
.padding()
}
func CGRectdescription(id: String, rect: CGRect) -> String {
return id + ": (" + String(format: "%.2f", rect.origin.x) + ", " + String(format: "%.2f", rect.origin.y) + ", " + String(format: "%.2f", rect.size.width) + ", " + String(format: "%.2f", rect.size.height) + ")"
}
}
Coordinate spaces declared by .coordinateSpace(...)
are only visible to the children of that modifier. The GeometryReader
cannot see .coordinateSpace(name: coordinateSpaceName)
because the GeometryReader
is its parent view. As a result, it uses the coordinate space of the window as a fallback.
If you put .coordinateSpace(...)
on the GeometryReader
, then the code works as expected. The origin
returned by frame(in:)
is (0, 0).
You can also put it on the VStack
, in which case frame(in:)
will return an origin of (0, 29). 29pt happens to be the distance between the top of the VStack
and the top of the GeometryReader
.