Unable to update/modify SwiftUI View's @state var

You should only change State of a view Inside it's own body block. If you need to change it from a parent view, you may want to pass the value to it from parent and make it Binding instead.

struct ContentView: View {
    @State private var message = "Hello"

    var body: some View {
        VStack {
            ExampleView(message: $message)
                .foregroundColor(Color.red)
                .padding()
            Button("Press me") {
                self.message = "Updated"
            }
        }
    }
}

struct ExampleView: View {
    @Binding var message: String

    var body: some View {
        Text(message)
    }
}

If you need to encapsulate messages inside the ExampleView, you can use a Bool (or an enum or etc) instead:

struct ContentView: View {
    @State private var updated = false

    var body: some View {
        VStack {
            ExampleView(isUpdated: $updated)
                .foregroundColor(Color.red)
                .padding()
            Button("Press me") {
                self.updated = true
            }
        }
    }
}

struct ExampleView: View {
    @Binding var isUpdated: Bool
    private var message: String { isUpdated ? "Updated" : "Hello" }

    var body: some View {
        Text(message)
    }
}

actually, the variable does get updated, but your Content view doesn't get informed about it. This is what happens:

  • ContentView gets called, it initializes coloredLabel with an ExampleView
  • you press the button in ContentView
  • self.coloredLabel.updateMessage() get's called
  • the message is printed
  • the variable self.coloredLabel.message is modified
  • ContentView does not get redrawn, as it isn't notified about the change
  • more specifically, coloredLabel inside your Stack doesn't get updated

now, you have different options: @State, @Binding and @PublishedObject, @ObservedObject. You need one of these Publishers, so your view actually notices that it needs to do something.

Either you draw a new ExampleView every time you press the button, in this case you can use a @State variable in ContentView:

struct ContentView: View {
    @State private var string = "Hello"

    var body: some View {
        VStack {
            ExampleView(message: string)
                .foregroundColor(Color.red)
                .padding()
            Button(action: {
                self.string = "Updated"
            }) {
                Text("Press me")
            }

        }
    }
}

struct ExampleView: View {
    var message: String

    var body: some View {
        Text(self.message)
    }
}

which is probably not what you want.

Next, you can use @Binding which was already suggested.

And last, you can use ObservableObject @ObservedObject, @Published

class ExampleState: ObservableObject {
    @Published var message: String = "Hello"
    func update() {
        message = "Updated"
    }
}

struct ContentView: View {
    @ObservedObject var state = ExampleState()

    var body: some View {
        VStack {
            ExampleView(state: state)
                .foregroundColor(Color.red)
                .padding()
            Button(action: {
                self.state.update()
            }) {
                Text("Press me")
            }

        }
    }
}

struct ExampleView: View {
    @ObservedObject var state: ExampleState

    var body: some View {
        Text(state.message)
    }
}

what this says is: class ExampleState: ObservableObject - this class has published variables that can be observed

to resume (that's how I understand it):

  • "Hey, ContentView and ExampleView: if state.message (any value that state publishes) changes, you need to redraw your body"
  • "And ExampleState: after updating your message variable, publish the new value!"

lastly - for completion - there is @EnvironmentObject, as well, that way you'd only have to pass the variable to the top-views and everything down the view hierarchy would inherit it.