SwiftUI relies on a concept called a "Single Source of Truth."
Because views are just structs, they are immutable by default. You cannot change their variables once they are created.
To make your UI interactive and updatable, you use property wrappers to tell SwiftUI: "Watch this piece of data, and redraw the screen if it changes."
The @State property wrapper is used for simple, local data that belongs entirely to one specific view.
When a @State variable changes, SwiftUI instantly invalidates the view and re-computes its body.
import SwiftUIstruct CounterView: View { // @State allows us to modify this struct property @State private var count = 0 var body: some View { VStack { Text("Count: \(count)") .font(.largeTitle) Button("Increment") { count += 1 // This triggers a UI update! } } } }
It is best practice to mark @State variables as private because they should only be modified by the view that owns them.
What if you want a child view to modify the @State owned by its parent view?
You use the @Binding property wrapper!
A @Binding doesn't hold its own data; it simply creates a two-way connection to a piece of data stored elsewhere.
struct ParentView: View {
@State private var isOn = false
var body: some View {
// The $ symbol creates a Binding to the state variable
ChildToggleView(isOn: $isOn)
}
}
struct ChildToggleView: View {
// This view expects a binding from its parent
@Binding var isOn: Bool
var body: some View {
Toggle("Enable Feature", isOn: $isOn)
}
}
Notice the $ symbol used in the parent view. The dollar sign extracts the binding connection from the state variable!
For data that needs to be shared across many different views in your app, SwiftUI provides other tools like @Environment and the Observable macro.
We will explore those as you dive deeper into advanced app architectures.
Which property wrapper should you use to store simple, private data that updates the local view when changed?