Labs ICT
Pro Login

State and Binding

Making SwiftUI views reactive.

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 →