Skip to content
SwiftMind

Binding

A read-write reference to state that lives somewhere else.

BeginnerNot started6 min
On this page

A Binding is a two-way connection to a value owned by another view. It doesn't store anything — it's a pair of closures, a getter and a setter, pointing at somebody else's storage.

That's what lets a child mutate a parent's state without owning it, and without the parent passing down a callback for every field. TextField, Toggle and Slider all take one, which is why they can write back the value you handed them.

You make one with the $ prefix on a @State property. Data flows down as a value, changes flow back up through the setter.

@Binding declares the dependency; $ creates one to pass on.
struct ParentView: View {    @State private var isOn = false      // owns the storage
    var body: some View {        VStack {            SettingRow(title: "Notifications", isOn: $isOn)   // $ projects a Binding            Text(isOn ? "On" : "Off")                         // parent sees the change        }    }}
struct SettingRow: View {    let title: String    @Binding var isOn: Bool              // no storage — a window onto the parent's
    var body: some View {        Toggle(title, isOn: $isOn)       // $ again, to pass it further down    }}
// A derived binding: transform on the way in and outextension Binding where Value == String {    var trimmed: Binding<String> {        Binding(get: { wrappedValue.trimmingCharacters(in: .whitespaces) },                set: { wrappedValue = $0 })    }}
// Previews and tests don't need real stateSettingRow(title: "Preview", isOn: .constant(true))
Show all 30 lines

Under the hood

Binding is a struct holding a get closure and a set closure. There's no storage in it at all, which is why passing one around is cheap and why a binding to a deallocated source is a real hazard rather than an impossible state.

The $ prefix is the property wrapper's *projected value*: @State exposes projectedValue of type Binding<Value>, and $count is sugar for _count.projectedValue. Any property wrapper can project something; @State, @Binding, @Bindable and @FocusState all project bindings.

Because it's just closures, you can build one by hand with Binding(get:set:) — useful for deriving a transformed binding, bridging to a non-SwiftUI model, or adapting an optional. .constant(_) is the same thing with a setter that discards, which is what previews want.

Quick check

What does the $ prefix actually give you?

  • A copy of the value
  • The property wrapper's projected value — for @State, a Binding to its storage
  • A reference to the view
  • An optional version of the value
Reveal answer

The property wrapper's projected value — for @State, a Binding to its storage

$count is sugar for _count.projectedValue. For @State and @Binding that projection is a Binding, which is a getter/setter pair pointing at the underlying storage. Other wrappers project other things — the $ is a general property-wrapper feature, not binding-specific syntax.

Interview question

When should a child view take a Binding rather than a plain value?

Think first, then reveal

When the child needs to write. A plain value is a one-way snapshot — the child can display it and nothing more. A Binding is a getter and setter pointing back at the owner's storage, so writes propagate.

The way I decide is by asking who owns the state. It should live with the view that's responsible for it — usually the highest view that cares — as @State. Anything below that either reads it, in which case a let is better because it documents that the child can't change anything, or writes it, in which case @Binding and pass $.

Getting this wrong produces a very characteristic bug: a toggle or text field that appears to work, animates correctly, and has no effect. There's no error, because passing a value where you meant a binding compiles fine — the child just ends up mutating its own copy.

The other reason to prefer let where you can is that it keeps the data flow readable. If every child takes a binding, you can't tell from a signature which ones actually mutate.

What an interviewer listens for

  • Ties the choice to whether the child writes
  • Places ownership at the highest view that cares
  • Prefers let for read-only children, and says why
  • Names the silent no-effect bug that results from getting it wrong

Key takeaway

A binding is a getter/setter pair pointing at someone else's storage. Pass $ when the child must write, and a plain value when it only reads.

Read these first

Related

0%
Quiz this concept