@State property isn't updated quickly enough?

I'm trying to create a SwiftUI app, and I'm encountering some surprising behavior. I have a background in React.js so I'm familiar with declarative UI systems, but I think that's working against me here because this isn't behaving as I would expect.

So what I'm trying to do with this code is create a button that changes some state values and causes a sheet to open from the bottom of the page and display a value.

struct ExampleView: View {
    @State var pageOpen: Bool = false
    @State var valueString: String = "Initial Value (you shouldn't see this)"

    var body: some View {
        NavigationView {
            Button(action: {
                valueString = "Testing"
                pageOpen = true
            }) {
                Text("Click me")
            }
        }
        .sheet(isPresented: $pageOpen) {
            let _ = print("pageOpen is \(pageOpen)")
            let _ = print("valueString is \(valueString)")
            Text(valueString)
        }
    }
}

This works the second or third time I run open the sheet, but the first time I click the button, it's like the variables haven't been updated yet. The values printed are still the initial values.

I could reiterate how I think this should work but clearly I'm misunderstanding something.

Edit: I'm using XCode 13, testing in the iOS 15 simulator.

Definitely weird. Try maybe adding .onChange(of: pageOpen) { print($0) } and .onChange(of: valueString) { print($0) } to the NavigationView, and see if it prints as expected there.

Usually in SwiftUI we try and not put any side effects in a ViewBuilder (which is what that .sheet modifier expects there) because it can do some weird things. We instead use specific tools like .onAppear {}, .task {} or here .onChange to perform these kind of small side effects.

1 Like

Adding .onChange(of: valueString) {_ in} to the navigation view fixes the problem, and the correct string is displayed on the screen.

I also sent a link to this question to a friend. He speculates that "it doesn’t know to refresh the view because you don’t have a dependency on valueString in the navigation view." So adding this dependency, in the form of a .onChange(of: valueString) causes everything to refresh.

I'm still not confidant enough to say that this is a bug; maybe there's a better solution. But for now I'm going to use this work-around.

Thank you very much for confirming that my understanding isn't totally flawed.

1 Like