SwiftUI’s @State
property wrapper is designed for simple data that is local to the current view, but as soon as you want to share data you need to take some important extra steps.
Let’s break this down with some code – here’s a struct to store a user’s first and last name:
struct User {
var firstName = "Bilbo"
var lastName = "Baggins"
}
We can now use that in a SwiftUI view by creating an @State
property and attaching things to $user.firstName
and $user.lastName
, like this:
struct ContentView: View {
@State private var user = User()
var body: some View {
VStack {
Text("Your name is \(user.firstName) \(user.lastName).")
TextField("First name", text: $user.firstName)
TextField("Last name", text: $user.lastName)
}
}
}
That all works: SwiftUI is smart enough to understand that one object contains all our data, and will update the UI when either value changes. Behind the scenes, what’s actually happening is that each time a value inside our struct changes the whole struct changes – it’s like a new user every time we type a key for the first or last name. That might sound wasteful, but it’s actually extremely fast.
Previously we looked at the differences between classes and structs, and there were two important differences I mentioned. First, that structs always have unique owners, whereas with classes multiple things can point to the same value. And second, that classes don’t need the mutating
keyword before methods that change their properties, because you can change properties of constant classes.
In practice, what this means is that if we have two SwiftUI views and we send them both the same struct to work with, they actually each have a unique copy of that struct; if one changes it, the other won’t see that change. On the other hand, if we create an instance of a class and send that to both views, they will share changes.
For SwiftUI developers, what this means is that if we want to share data between multiple views – if we want two or more views to point to the same data so that when one changes they all get those changes – we need to use classes rather than structs.
So, please change the User
struct to be a class. From this:
struct User {
To this:
class User {
Now run the program again and see what you think.
Spoiler: it doesn’t work any more. Sure, we can type into the text fields just like before, but the text view above doesn’t change.
When we use @State
, we’re asking SwiftUI to watch a property for changes. So, if we change a string, flip a Boolean, add to an array, and so on, the property has changed and SwiftUI will re-invoke the body
property of the view.
When User
was a struct, every time we modified a property of that struct Swift was actually creating a new instance of the struct. @State
was able to spot that change, and automatically reloaded our view. Now that we have a class, that behavior no longer happens: Swift can just modify the value directly.
Remember how we had to use the mutating
keyword for struct methods that modify properties? This is because if we create the struct’s properties as variable but the struct itself is constant, we can’t change the properties – Swift needs to be able to destroy and recreate the whole struct when a property changes, and that isn’t possible for constant structs. Classes don’t need the mutating
keyword, because even if the class instance is marked as constant Swift can still modify variable properties.
I know that all sounds terribly theoretical, but here’s the twist: now that User
is a class the property itself isn’t changing, so @State
doesn’t notice anything and can’t reload the view. Yes, the values inside the class are changing, but @State
doesn’t monitor those, so effectively what’s happening is that the values inside our class are being changed but the view isn’t being reloaded to reflect that change.
We can fix this problem with one small change: I'd like you to add the line @Observable
before the class. It should look like this:
@Observable
class User {
And now our code will work again. To understand why, let's explore what @Observable
actually does…
SAVE 50% To celebrate Black Friday, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.