One of the first decisions SwiftUI developers need to make is which of the available property wrappers to use to store data. Especially in iOS 14, where the whole app lifecycle can be written with SwiftUI, storing your data the right way is essential to your app running and behaving predictably and bug-free.
The app I made to test these different property wrappers
The challenge in making a SwiftUI app is technically all four of @State, @StateObject, @ObservedObject and @EnvironmentObject will superficially “work”. Your app will compile, and you may even get the behaviour you are after even when using the wrong property wrapper for the situation.
But, if used incorrectly, you may find your view doesn’t update when your data updates. Or, your data persists for longer than you expect it to. Or, your data doesn’t persist at all.
Let’s break it down in this story, beginning with @State.
_State: _A property wrapper type that can read and write a value managed by SwiftUI.
This is the definition of @State from Apple. But what does that mean?
State is the simplest source of truth your app can have. It is designed to contain simple value types, such as Ints, Strings, and Bools. It is not designed for more complex, reference types, such as any classes or structs you define yourself and use within your app.
// THIS IS GOOD
@State private var isPlaying = false
// THIS IS BAD
@State private var isPlaying = IsPlaying()
A good use of state versus a bad use of state
Apple even says this — to use state while the values are simple, before you add them to your model:
You might also find this kind of storage convenient while you prototype, before you’re ready to make changes to your app’s data model.
@State works by re-computing the body variable of your view any time it updates. So if you have some State in your view that keeps track of an integer, and you add 1 to the integer, your State will see this and re-render the view. As the view uses this state, you will see the number on your screen update.
Like I said earlier, this works as described for simple value types like integer, string or boolean. However, you will find that it is _possible _to have @State keep track of a complex object. I’m going to refer to the following object a number of times throughout this story as an example:
class TestObject {
var num: Int = 0
}
This is how I’m using it:
struct StateTestView: View {
@State var state = TestObject()
var body: some View {
VStack {
Text("State: \(state.num)")
Button("Increase state", action: {
state.num += 1
print("State: \(state.num)")
})
}
.onChange(of: state.num) { newState in
print("State: \(newState)")
}
}
}
This will not throw any errors. What I’ve done is created a new copy of TestObject()
and marked it with the @State
property wrapper, which tells SwiftUI that I want this view to keep track of it.
What do you think happens when I call state.num += 1
, as per line 8?
Well, we see in the output window with **line 9 **that state.num
has incremented by 1, but in the view in our app, the value of state
hasn’t changed. Why is this?
#ios-app-development #swift #swiftui #ios #observableobject