I am using GeometryReader
in SwiftUI to track the frame of a Color.black
background. However, when I resize the window, the size retrieved from GeometryReader
(via geo.frame(in: .local)
) unexpectedly shrinks, even though the Color.black
background visually remains the correct size.
Here’s the code I am using:
struct ContentView: View {
@State private var rect: CGRect = CGRect()
var body: some View {
GeometryReader { geo in
ZStack {
Color.black
.onAppear {
rect = geo.frame(in: .local)
}
.onChange(of: geo.frame(in: .local)) { oldValue, newValue in
rect = newValue
}
Color.red.frame(width: rect.width, height: 2.0)
Color.blue.frame(width: 2.0, height: rect.height)
Color.green
.frame(width: 300.0, height: 50.0)
.offset(y: -100.0)
}
}
.padding(300.0)
}
}
Why is the size value from the GeometryReader shrinking during a window resize, and how can I ensure it always matches the size of the Color.black background?
This is because the size of the GeometryReader
is more flexible than the size of the Color.black
. It can change to a size that is smaller than the Color.black
. The Color.black
is always as large as the ZStack
, which is at least as large as the Color.green
.
You can see the size of the GeometryReader
by adding a .border(.red)
to the it. before the padding
.
So one way to solve this is just not to consider the cases where the size of the GeometryReader
is smaller than the Color.black
. It's not very clear what should the view look like when that happens, anyway. You can do this by setting a minimum size:
GeometryReader { geo in
// ...
}
.frame(minWidth: 300, minHeight: 50)
.padding(300.0)
If you want the Color.black
to be resizable to a size smaller than the Color.green
, do not put them in the same ZStack
. Use an overlay
/background
to put one on top of/below the other.
GeometryReader { geo in
Color.black
.onAppear {
rect = geo.frame(in: .local)
}
.onChange(of: geo.frame(in: .local)) { oldValue, newValue in
rect = newValue
}
.overlay {
ZStack {
Color.red.frame(width: rect.width, height: 2.0)
Color.blue.frame(width: 2.0, height: rect.height)
Color.green
.frame(width: 300.0, height: 50.0)
.offset(y: -100.0)
}
}
}
.padding(300.0)
Other notes:
onGeometryChange
instead of a GeometryReader
:Color.black
// .frame(minWidth: 300, minHeight: 50) // you can still add this if you want
.onGeometryChange(for: CGRect.self) {
$0.frame(in: .local)
} action: { newValue in
rect = newValue
}
.overlay {
ZStack {
Color.red.frame(width: rect.width, height: 2.0)
Color.blue.frame(width: 2.0, height: rect.height)
Color.green
.frame(width: 300.0, height: 50.0)
.offset(y: -100.0)
}
}
.padding(300.0)
Color
s naturally expand to their container's size, so you don't actually need a GeometryReader
here at all. I will assume that this is just a toy example and what you are actually doing does require a GeometryProxy
.frame(in: .local)
will almost always return a rect with origin (0, 0). Consider using a CGSize
if you just need the size.