@State & @Binding

Reviewed & published by Brayan K

SwiftUI views are value types, so how can they change? The answer is property wrappers. @State gives a view its own mutable source of truth, and @Binding lets a child read and write state that a parent owns. Master these and your UIs come alive.

Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

Building iOS Apps with SwiftUI • Intermediate

What You'll Learn in This Lesson

1️⃣ @State — A View's Own Source of Truth

Mark a property with @State to give the view a small piece of mutable data it owns. SwiftUI stores that value for you (outside the struct) and watches it. When you change it, SwiftUI re-runs body and redraws. Keep it private — it belongs to this view alone.

import SwiftUI

// @State gives a view its OWN mutable value. SwiftUI stores it outside the
// struct and watches it: when it changes, SwiftUI re-runs body automatically.
struct CounterView: View {
    // local source of truth — conventionally private
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1          // changing @State refreshes the view
            }
        }
    }
}

2️⃣ @Binding — Sharing Writable State

When a child view needs to read and write state owned by a parent, use @Binding. It's a two-way connection: the parent keeps the single source of truth, but the child can update it. You hand the child a binding using the $ prefix on the parent's @State.

3️⃣ The $ Prefix — Projected Values

Every property wrapper can expose a projected value via the $ prefix. For @State and @Binding, the projected value is a Binding. Use the plain name for the value, and $name when a control like TextField must write back.

import SwiftUI

// The $ prefix gives you the PROJECTED VALUE of a wrapped property —
// for @State and @Binding that projected value is a Binding.
struct NameEditor: View {
    @State private var name = "Ada"

    var body: some View {
        VStack {
            // 'name'  -> the String value
            Text("Hello, \(name)")

            // '$name' -> a Binding<String> the TextField writes back into
            TextField("Your name", text: $name)
        }
    }
}

Your turn. Fill in the blanks to wire up local state and pass a binding to a child.

// 🎯 YOUR TURN — fill in each ___ then preview in Xcode.
import SwiftUI

struct LikeButton: View {
    // 1) Declare local mutable state 'liked' starting at false
    ___ private var liked = false   // 👉 the property wrapper for local state

    var body: some View {
        Button(liked ? "♥ Liked" : "♡ Like") {
            liked.___()             // 👉 method that flips a Bool
        }
    }
}

struct ParentView: View {
    @State private var liked = false

    var body: some View {
        // 2) Pass 'liked' to a child as a binding using the right prefix
        ChildLikeRow(liked: ___liked)   // 👉 the projected-value prefix
    }
}

struct ChildLikeRow: View {
    @Binding var liked: Bool
    var body: some View { Text(liked ? "Liked" : "Not yet") }
}

Pro Tips

Common Errors (and the fix)

Mini-Challenge: Stepper Widget

Build a parent view that owns an @State quantity starting at 1. Create a child QuantityStepper that takes a @Binding var quantity: Int and shows "−" and "+" buttons that decrease and increase it. Pass the state down with $, and show the live total in the parent.

🎉 Lesson Complete!

Practice quiz

What does the @State property wrapper provide?

  • A shared global value
  • Local mutable state owned by a view
  • A database connection
  • A constant value

Answer: Local mutable state owned by a view. @State gives a view its own piece of mutable state that SwiftUI stores and watches for changes.

What happens when an @State value changes?

  • Nothing until you call refresh()
  • SwiftUI re-computes the view's body
  • The app crashes
  • The value is discarded

Answer: SwiftUI re-computes the view's body. Changing @State invalidates the view, so SwiftUI re-runs body and updates the screen.

Why is @State usually marked private?

  • It is required by the compiler
  • Because it is local source of truth owned by that view
  • To make it global
  • It cannot be private

Answer: Because it is local source of truth owned by that view. @State is the view's own source of truth, so it's conventionally private to that view.

What does @Binding create?

  • A copy of a value
  • A constant
  • A new source of truth
  • A two-way connection to state owned elsewhere

Answer: A two-way connection to state owned elsewhere. @Binding is a read-write reference to state that lives in a parent view, enabling two-way data flow.

How do you pass an @State value as a binding to a child?

  • By writing state.binding
  • By prefixing the name with a dollar sign, like $name
  • By using @Binding twice
  • You cannot pass it

Answer: By prefixing the name with a dollar sign, like $name. The $ prefix (projected value) turns an @State into a Binding you can hand to a child view.

Inside a child, what does the plain binding name (without $) give you?

  • The current underlying value
  • The Binding wrapper itself
  • Always nil
  • A copy that won't update

Answer: The current underlying value. Reading a @Binding by its name yields the underlying value; writing to it updates the parent's state.

Why are SwiftUI views value types (structs)?

  • So they can be subclassed
  • To allow inheritance
  • So SwiftUI can cheaply create and diff them
  • Because classes are banned

Answer: So SwiftUI can cheaply create and diff them. Views are lightweight structs, so SwiftUI can recreate and compare them efficiently every update.

Since a view is a struct, why can its body still 'mutate' a counter?

  • Structs are secretly classes
  • @State stores the value outside the struct so SwiftUI manages it
  • It does not actually change
  • Swift copies the whole view

Answer: @State stores the value outside the struct so SwiftUI manages it. @State stores its value in storage managed by SwiftUI, not in the struct itself, so updates persist across recreations.

Which declaration is correct for a counter starting at 0?

  • @State var count = 0
  • @Binding var count = 0
  • @State let count = 0
  • var @State count = 0

Answer: @State var count = 0. @State var count = 0 declares mutable local state with an initial value.

When should you use @Binding instead of @State?

  • Always, they are identical
  • When the child needs to read AND change state owned by a parent
  • Only for constants
  • Never in SwiftUI

Answer: When the child needs to read AND change state owned by a parent. @Binding is for a child view that must read and write state whose source of truth lives in a parent.

Continue this course

Frequently asked questions

What is @State?

@State is a property wrapper that gives a view its own local, mutable source of truth. SwiftUI stores the value outside the view struct and watches it; whenever it changes, SwiftUI re-computes the view's body and updates the screen. It's ideal for small, view-local data like a counter, a toggle, or text being typed.

Why is @State usually private?

Because @State is the view's own source of truth — it is owned by that single view and shouldn't be set from outside. Marking it private documents that ownership and prevents other code from mutating it directly. To let a child change it, you pass a Binding instead.

What is @Binding and how is it different from @State?

@Binding is a two-way reference to state that is owned elsewhere (usually a parent's @State). The child reads and writes through the binding, but the parent remains the single source of truth. @State creates and owns state; @Binding borrows it. Use @Binding when a reusable child view must mutate a parent's value.

What does the $ prefix do?

The $ prefix exposes a property wrapper's projected value. For @State and @Binding, that projected value is a Binding. So $count turns an @State Int into a Binding<Int> you can pass to a child view or to a control like TextField or Toggle that needs to write the value back.

If a view is a struct (a value type), how can it change?

The view struct itself is immutable and gets recreated often. @State works around this by storing its value in storage that SwiftUI manages, not inside the struct. So mutating an @State property updates that external storage and survives the struct being recreated — that's why SwiftUI can re-run body with the new value.