Skip to content

How to use @Binding property wrapper in SwiftUI | SwiftUI Bootcamp #22

You've built a child view that needs to change a value owned by its parent — but passing a let constant gives the child read-only access, and duplicating state creates two conflicting copies. @Binding solves this precisely: it lets a child view both read and write a value it doesn't own.

What You'll Learn

  • The difference between @State (ownership) and @Binding (shared reference)
  • How to pass a binding from a parent view using the $ prefix
  • When to use @Binding versus creating separate local @State in the child

Mental Model

Think of @Binding like a remote control. The television owns its own channel number — that's the @State in the parent. The remote control doesn't store the channel itself; it holds a reference to the TV's channel and can read it or change it from anywhere in the room. @Binding is the remote. The $ prefix in Swift is the button on the back that says "hand me the remote, not just the current channel number."

If you passed the channel number by value (let channel = tv.channel), pressing a button on the remote wouldn't actually change the TV — you'd only change your local copy. @Binding ensures pressing the button reaches back to the real source of truth.

Detailed Explanation

In SwiftUI, @State creates a source of truth: SwiftUI stores the value, and any change to it triggers a re-render of the owning view and its children. But when you extract a child view (like ButtonView), that child gets its own struct instance. If you pass the value as a let, the child can read but not write. If you give the child its own @State, you now have two separate values that can drift apart.

@Binding bridges this gap. You declare @Binding var backgroundColor: Color in the child. The parent passes $backgroundColor — the binding — instead of backgroundColor (the current value). Now both parent and child reference the same stored value. When the child writes to the binding, the parent re-renders, and because the child is part of the parent's body, the child re-renders too.

The $ prefix is how you access the binding wrapper around a @State variable. Without $, you get the value itself (Color). With $, you get the binding — a Binding to the Color — which is what a @Binding property expects.

A child view can mix @Binding and @State. @Binding connects a value owned by the parent. @State manages values owned by the child (like buttonColor in this example — the parent has no reason to know what color the button currently is, so the child owns it privately).

Code Structure

BindingBootcamp.swift contains two structs: BindingBootcamp (the parent, which owns backgroundColor and title as @State) and ButtonView (the child, which receives them as @Binding and adds its own private @State var buttonColor). The interaction demonstrates all three property wrapper combinations in one example.

Complete Code

BindingBootcamp.swift

swift
import SwiftUI

struct BindingBootcamp: View {
    
    @State var backgroundColor: Color = Color.green // source of truth — this view owns these values
    @State var title: String = "Title"
    
    var body: some View {
        ZStack {
            backgroundColor
                .edgesIgnoringSafeArea(.all)
            
            VStack {
                Text(title)
                    .foregroundColor(.white)
                
                ButtonView(backgroundColor: $backgroundColor, title: $title) // $ passes a Binding, not just the value
            }
        }
    }
}

struct ButtonView: View {
    
    @Binding var backgroundColor: Color // references the parent's @State — changes here update the parent
    @State var buttonColor: Color = Color.blue // owned by this child only — the parent never sees this
    @Binding var title: String
    
    var body: some View {
        Button(action: {
            backgroundColor = Color.orange  // writes through the binding to the parent's @State
            buttonColor = Color.pink        // writes to local @State — only this view re-renders for this change
            title = "NEW TITLE!!!!!!!"      // writes through the binding to the parent's @State
        }, label: {
            Text("Button")
                .foregroundColor(.white)
                .padding()
                .padding(.horizontal)
                .background(buttonColor)   // driven by local @State
                .cornerRadius(10)
        })
    }
}

struct BindingBootcamp_Previews: PreviewProvider {
    static var previews: some View {
        BindingBootcamp()
    }
}

Code Walkthrough

  1. @State var backgroundColor in BindingBootcamp — SwiftUI allocates persistent storage for this value and ties the view's lifetime to it. The parent is the single source of truth.

  2. ButtonView(backgroundColor: $backgroundColor, title: $title) — The $ converts each @State into a Binding to the Color and a Binding to the String. These are not copies — they are references that write back to the same storage.

  3. @Binding var backgroundColor in ButtonView — This declaration says "I expect someone to hand me a reference to a Color. I don't own storage; I borrow it." The child can read and write the value.

  4. @State var buttonColor: Color = Color.blue — Private state that only ButtonView cares about. The parent has no reason to track this, so the child owns it. This is the right choice: use @State for child-only UI state, @Binding for values that the parent also needs.

  5. backgroundColor = Color.orange inside the button action — This writes through the binding. SwiftUI detects the change in BindingBootcamp's storage and re-renders the parent (and all its children, including ButtonView).

  6. buttonColor = Color.pink — This writes to the child's own @State. Only ButtonView re-renders for this mutation — a more efficient update path.

Common Mistakes

Mistake: Declaring @Binding without passing a $ at the call site
If you write ButtonView(backgroundColor: backgroundColor) (no $), Swift passes the current Color value, not a binding. The compiler will error because the initializer expects a Binding to a Color, not a bare Color.

Mistake: Using @Binding when the child view doesn't need to write back
If the child only reads the value, a simple let is sufficient and more communicative. @Binding implies the child modifies the value; a let signals read-only access. Use the weakest tool that works.

Mistake: Creating a @State in the child to "sync" with the parent
This creates two separate values that start equal but diverge the moment either changes. There is no automatic synchronization. If you need two-way communication, pass a @Binding.

Key Takeaways

  • @State declares ownership; @Binding declares a reference to someone else's ownership.
  • The $ prefix turns a @State variable into a binding that you can pass to child views.
  • A single view can mix @Binding (for parent-owned values) and @State (for values it owns privately) — both can coexist without conflict.

Last updated: June 27, 2026

Released under the MIT License.