Skip to content

SwiftUI @State and @Binding explained

October 29, 2026 · 2 min read

Use @State in the view that owns a value, and @Binding in a child view that needs to change it. The parent passes its state down with a $ in front.

A quantity stepper

import SwiftUI

struct QuantityControl: View {
    @Binding var quantity: Int

    var body: some View {
        Stepper("Quantity: \(quantity)", value: $quantity, in: 1...10)
    }
}

struct DrinkView: View {
    @State private var quantity = 1

    var body: some View {
        VStack(spacing: 16) {
            Text("Latte × \(quantity)")
            QuantityControl(quantity: $quantity)
        }
        .padding()
    }
}

#Preview {
    DrinkView()
}

Output (in the canvas or simulator): it starts as "Latte × 1" with "Quantity: 1". Tap + twice and both labels read 3. Tap − and they read 2. The stepper stops at 1 and at 10.

What is happening

  • @State private var quantity = 1 makes DrinkView the owner. SwiftUI stores the value and runs body again whenever it changes.
  • @Binding var quantity: Int in QuantityControl stores no number. It is read and write access to a value someone else owns.
  • $quantity in DrinkView is the binding to that state. Passing it to QuantityControl(quantity:) hands the child that access.
  • Inside the child, $quantity passes the binding on to Stepper, which needs it to change the value when you tap + or −.

There is only one copy of the number. When the stepper changes it, the parent's state changes, and both views redraw.

Common mistake

Using @State in the child. If QuantityControl declared @State private var quantity = 1, it would keep its own number. The stepper would work, but "Latte × 1" in the parent would never change. Declare the value once, in the owner, and use @Binding everywhere else.

Forgetting the $. Writing QuantityControl(quantity: quantity) passes a plain Int, and Xcode reports a type mismatch. Write $quantity to pass the binding.

Takeaway

One view owns the value with @State. Every child that needs to change it takes a @Binding, and you pass it with $.