So it's totally possible to update a @State
inside a GeometryReader
. The solution is simple. However, there's a caveat:
you might end up with an infinite loop
(nothing too troublesome, I'll present a solution here)
You'll just need a DispatchQueue.main.async
and explicitly declare the type of the view inside GeometryReader
. If you execute the View below (don't forget to stop it) you'll see that it never stops updating the value of the Text
.
NOT THE FINAL SOLUTION:
struct GenericList: View {
@State var timesCalled = 0
var body: some View {
GeometryReader { geometry -> Text in
DispatchQueue.main.async {
timesCalled += 1 // infinite loop
}
return Text("\(timesCalled)")
}
}
}
This happens because the View will "draw" the GeometryReader
, which will update a @State
of the View. Thus, the new @State
invalidates the View causing the View to be redrawn. Consequently going back to the first step (drawing the GeometryReader
and updating the state).
To solve this you need to put some constraints in the draw of the GeometryReader
. Instead of returning your View inside the GeometryReader
, draw it then add the GeometryReader
as a transparent overlay or background. This will have the same effect but you'll be able to put constraints in the presentation.
Personally, I'd rather use an overlay because you can add as many as you want. Note that an overlay does not permit an if else
inside of it, but it is possible to call a function. That's why there's the func geometryReader()
below. Another thing is that in order to return different types of Views you'll need to add @ViewBuilder
before it.
In this code, the GeometryReader is called only once and you get the @State var timesCalled updated.
FINAL SOLUTION:
struct GenericList: View {
@State var timesCalled = 0
@ViewBuilder
func geometryReader() -> some View {
if timesCalled < 1 {
GeometryReader { geometry -> Color in
DispatchQueue.main.async {
timesCalled += 1
}
return Color.clear
}
} else {
EmptyView()
}
}
var body: some View {
Text("\(timesCalled)")
.overlay(geometryReader())
}
}
Note: you don't need to put the same constraints, for example, in my case, I wanted to move the view with the drag gesture. So, I've put the constraints to start when the user touches down and to stop when the user ends the drag gesture.