SwiftUI relies upon the @State variable causing the body getter to be recalculated when it changes. For this to work, the body getter must depend in certain definite ways on the @State variable. The problem in your code is that it doesn't.
To see this, we can reduce your code to a simpler example:
struct ContentView: View {
@State var message = "Hey"
@State var show: Bool = false
var body: some View {
VStack {
Button("Test") {
message = "Ho"
show = true
}
}
.sheet(isPresented: $show) {Text(message)}
}
}
We change message to Ho, but when the sheet is presented, it still says Hey. This is because nothing happened to make the body recalculate. You might say: What about the phrase Text(message)? Yes, but that's in a closure; it has already been calculated, and message has already been captured.
To see that what I'm saying is right, just add a Text displaying message directly to the main interface:
struct ContentView: View {
@State var message = "Hey"
@State var show: Bool = false
var body: some View {
VStack {
Button("Test") {
message = "Ho"
show = true
}
Text(message)
}
.sheet(isPresented: $show) {Text(message)}
}
}
Now your code works! Of course, we are also displaying an unwanted Text in the interface, but the point is, that plain and simple Text(message), not in a closure, is sufficient to cause the whole body getter to be recalculated when message changes. So we have correctly explained the phenomenon you're asking about.
So what's the real solution? How can we get the content closure to operate as we expect without adding an extra Text to the interface? One way is like this:
struct ContentView: View {
@State var message = "Hey"
@State var show: Bool = false
var body: some View {
VStack {
Button("Test") {
message = "Ho"
show = true
}
}
.sheet(isPresented: $show) {[message] in Text(message)}
}
}
By including message in the capture list for our closure, we make the body getter depend on the message variable, and now the code behaves as desired.