I'm unable to update the ExampleView's message
var even though I can see updateMessage()
is being called. Here is my simplified/convoluted SwiftUI example of Playground code that isn't working. The message
var does not get updated when called in updateMessage()
. As a result, the UI Text()
is not updated either.
Why is the @State var message
not updating? What is the correct way to update it?
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
let coloredLabel = ExampleView()
var body: some View {
VStack {
coloredLabel
.foregroundColor(Color.red)
.padding()
Button(action: {
self.coloredLabel.updateMessage()
}) {
Text("Press me")
}
}
}
}
struct ExampleView: View {
@State private var message: String = "Hello"
var body: some View {
Text(self.message)
}
func updateMessage() {
print("updateMessage ran") // this prints
self.message = "Updated"
}
}
PlaygroundPage.current.setLiveView(ContentView())
In case we have to modify state when another state is known, we can encapsulate all those states in ObservableObject and use onReceive to check the state we want to act on. Modifying state during view update, this will cause undefined behavior.
With @State, you tell SwiftUI that a view is now dependent on some state. If the state changes, so should the User Interface. It's a core principle of SwiftUI: data drives the UI.
To add the pull to refresh functionality to our SwiftUI List, simply use the . refreshable modifier. List(emojiSet, id: \. self) { emoji in Text(emoji) } .
You should only change State
of a view inside its 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:
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):
ContentView
and ExampleView
: if state.message
(any value that state
publishes) changes, you need to redraw your body"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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With