State and Binding
SwiftUI views are immutable — once created, they don't change. But your app needs to respond to user taps, text input, and data updates. That's where @State and @Binding come in. They let you create reactive UIs that automatically update when data changes.
Think of it this way: the view is a snapshot of your data. When the data changes, SwiftUI redraws the view. You don't manually update UI elements — you just change the data, and SwiftUI handles the rest.
@State for Local State
@State is a property wrapper that marks a value as owned by the view. When @State changes, SwiftUI rebuilds that view. It's perfect for simple toggles, counters, and form inputs that only affect one screen.
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
.font(.largeTitle)
Button("Add One") {
count += 1
}
}
}
}
The key rule: @State should always be private. It's owned by the view and shouldn't be accessible from outside. If you need to share state between views, use @Binding instead.
@Binding for Shared State
@Binding lets a child view read and write a value owned by a parent view. The child doesn't own the data — it just has a two-way connection to it. Think of @Binding as a pointer to the parent's @State.
struct ToggleButton: View {
@Binding var isOn: Bool
var body: some View {
Button(isOn ? "ON" : "OFF") {
isOn.toggle()
}
}
}
struct ParentView: View {
@State private var lightIsOn = false
var body: some View {
ToggleButton(isOn: $lightIsOn)
}
}
The dollar sign ($) creates the binding. When the child toggles isOn, the parent's lightIsOn changes too. This is how data flows down and events flow up in SwiftUI.
@Observable for Complex State
For more complex apps, @Observable (iOS 17+) lets you create model classes that automatically notify views when properties change. It's cleaner than the older ObservableObject approach.
@Observable class UserSession {
var isLoggedIn = false
var username = ""
var favorites: [String] = []
func login(name: String) {
username = name
isLoggedIn = true
}
}
struct ProfileView: View {
var session: UserSession
var body: some View {
if session.isLoggedIn {
Text("Welcome, \(session.username)!")
} else {
Button("Log In") {
session.login(name: "Alice")
}
}
}
}
@Observable is the modern way to manage shared state in SwiftUI apps. It replaces ObservableObject and @Published for most use cases.
When to Use Which
Use @State for simple, view-local data like toggles, text field content, and animation states. Use @Binding when a child view needs to modify a parent's @State. Use @Observable for app-wide or complex state like user sessions, shopping carts, or data fetched from APIs. The pattern: keep state as close to where it's needed as possible. Don't lift state up unless you have to.
Try it Yourself →