SwiftUI’s @State
property wrapper lets us modify our view structs freely, which means as our program changes we can update our view properties to match.
However, things are a little more complex with user interface controls. For example, if you wanted to create an editable text box that users can type into, you might create a SwiftUI view like this one:
struct ContentView: View {
var body: some View {
Form {
TextField("Enter your name")
Text("Hello, world!")
}
}
}
That tries to create a form containing a text field and a text view. However, that code won’t compile because SwiftUI wants to know where to store the text in the text field.
Remember, views are a function of their state – that text field can only show something if it reflects a value stored in your program. What SwiftUI wants is a string property in our struct that can be shown inside the text field, and will also store whatever the user types in the text field.
So, we could change the code to this:
struct ContentView: View {
var name = ""
var body: some View {
Form {
TextField("Enter your name", text: name)
Text("Hello, world!")
}
}
}
That adds a name
property, then uses it to create the text field. However, that code still won’t work because Swift needs to be able to update the name
property to match whatever the user types into the text field, so you might use @State
like this:
@State private var name = ""
But that still isn’t enough, and our code still won’t compile.
The problem is that Swift differentiates between “show the value of this property here” and “show the value of this property here, but write any changes back to the property.”
In the case of our text field, Swift needs to make sure whatever is in the text is also in the name
property, so that it can fulfill its promise that our views are a function of their state – that everything the user can see is just the visible representation of the structs and properties in our code.
This is what’s called a two-way binding: we bind the text field so that it shows the value of our property, but we also bind it so that any changes to the text field also update the property.
In Swift, we mark these two-way bindings with a special symbol so they stand out: we write a dollar sign before them. This tells Swift that it should read the value of the property but also write it back as any changes happen.
So, the correct version of our struct is this:
struct ContentView: View {
@State private var name = ""
var body: some View {
Form {
TextField("Enter your name", text: $name)
Text("Hello, world!")
}
}
}
Try running that code now – you should find you can tap on the text field and enter your name, as expected.
Before we move on, let’s modify the text view so that it shows the user’s name directly below their text field:
Text("Your name is \(name)")
Notice how that uses name
rather than $name
? That’s because we don’t want a two-way binding here – we want to read the value, yes, but we don’t want to write it back somehow, because that text view won’t change.
So, when you see a dollar sign before a property name, remember that it creates a two-way binding: the value of the property is read, but also written.
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.