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
@Bindingversus creating separate local@Statein 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
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
@State var backgroundColorinBindingBootcamp— SwiftUI allocates persistent storage for this value and ties the view's lifetime to it. The parent is the single source of truth.ButtonView(backgroundColor: $backgroundColor, title: $title)— The$converts each@Stateinto aBindingto theColorand aBindingto theString. These are not copies — they are references that write back to the same storage.@Binding var backgroundColorinButtonView— 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.@State var buttonColor: Color = Color.blue— Private state that onlyButtonViewcares about. The parent has no reason to track this, so the child owns it. This is the right choice: use@Statefor child-only UI state,@Bindingfor values that the parent also needs.backgroundColor = Color.orangeinside the button action — This writes through the binding. SwiftUI detects the change inBindingBootcamp's storage and re-renders the parent (and all its children, includingButtonView).buttonColor = Color.pink— This writes to the child's own@State. OnlyButtonViewre-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
@Statedeclares ownership;@Bindingdeclares a reference to someone else's ownership.- The
$prefix turns a@Statevariable 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