@Binding — What It Is, How It Works, and Examples in SwiftUI

Author: IT Sectr Published: 2026-06-19 Reading time: 7 min

@Binding is a Property Wrapper in SwiftUI that creates a reference to data owned by another component. Binding does not store the value itself — it only provides access to an existing source of truth through the $ projection. According to Apple Developer Documentation (2025), Binding provides reactive two-way communication between a parent and child view without directly owning the data. @Binding is a key mechanism for passing mutable state down the hierarchy.

Key Takeaways

  • @Binding — Property Wrapper for creating a reference to the parent view's state
  • No Ownership — Binding does not store data, only provides access to the source of truth
  • $ Projection — $stateValue creates a Binding from @State or @StateObject
  • Two-Way Binding — changes in the child view are immediately reflected in the parent
  • Binding.constant — fixed value for prototyping without feedback

What Is @Binding in SwiftUI?

@Binding is a Property Wrapper that creates a two-way connection between a property stored in the parent view and a child component. The main difference between Binding and @State: Binding does not own the data. It only reads and writes values through the true source — @State, @StateObject, or another Binding in the parent. Without Binding, child views could not modify ancestor state without callbacks or delegates.

Binding is implemented as a structure with two properties: wrappedValue (the current value) and projectedValue (the Binding itself, accessible via $). When a child view changes wrappedValue through Binding, SwiftUI propagates the change to the data source and redraws all dependent views. This happens synchronously within the current update cycle.

An important feature: @Binding is not limited to single-level passing. Binding can be passed through multiple hierarchy levels — each child component receives a reference to the same data source. A change at any level triggers a single update of all bound views.

How Two-Way Binding Works

The mechanism of two-way binding through @Binding is built on Property Wrapper projections. When a parent declares @State var value: T, SwiftUI automatically generates the $value projection of type Binding<T>. By passing $value to a child component with @Binding var value: T, you connect both views to the same memory cell. Any write through Binding in the child view triggers a redraw of both components.

swift
struct SliderContainer: View {
    @State private var value: Double = 0.5

    var body: some View {
        VStack {
            Text("Value: \(value)")
            SliderView(value: $value)
        }
    }
}

struct SliderView: View {
    @Binding var value: Double

    var body: some View {
        Slider(value: $value, in: 0...1)
    }
}

In the example, SliderContainer owns @State value, and SliderView receives the Binding through $value. The Slider inside SliderView is bound to this Binding. When the slider is dragged, it changes the value through Binding, which automatically updates @State in SliderContainer, and both views display the current number. The entire chain works without a single callback or notification.

To create a Binding from @StateObject or @ObservedObject, the same projection is used: $object.property gives Binding<PropertyType>. This allows passing individual properties of ObservableObject to child views without passing the entire object. This approach provides tighter coupling and prevents unnecessary redraws.

@Binding vs Callbacks: Which to Choose

Before SwiftUI, the standard way to pass changes up the hierarchy was through callbacks and delegates: the parent passed a closure, and the child component invoked it on change. @Binding offers an alternative with less code and a more declarative syntax. Instead of passing a completion closure, you simply pass $stateValue.

Criterion@BindingCallbacks
CodeOne annotation + $Closure + invocation
Multi-levelAutomaticClosure chain
TestingBinding(value:constant)Mock closures
ReadabilityHighMedium
FlexibilityData onlyAny logic

Use @Binding when the child view only needs to read and modify a value. If side effects are required on change (validation, logging, network request), combine Binding with a callback: pass Binding for data and a closure for events. For example, a TextField can bind to a Binding, while onChange triggers validation.

@Binding Usage Patterns in Projects

@Binding is used in several typical scenarios. The first — custom controls: switches, sliders, color pickers, and other interactive elements accept Binding for two-way synchronization. The second — modal windows: the sheet display flag is passed as Binding, allowing the child view to close itself via presentationMode or direct setting.

The third pattern — forms with separation. If a form consists of many fields, each field can be extracted into a separate component that accepts a Binding for its value. This simplifies testing and reuse of fields across different forms. The parent component remains the sole owner of the entire form model.

swift
struct FormField: View {
    let title: String
    @Binding var text: String

    var body: some View {
        VStack(alignment: .leading) {
            Text(title).font(.caption)
            TextField("Enter \(title.lowercased())", text: $text)
                .textFieldStyle(.roundedBorder)
        }
    }
}

The FormField component accepts a title and a Binding to a string. It displays a label and a TextField bound to the passed Binding. Any form can use FormField multiple times by passing $property for each field. This reduces markup duplication and centralizes text field styling.

Creating Custom Bindings

SwiftUI allows creating Binding manually through the Binding(get:set:) initializer. This is useful when logic needs to be added on value read or write. For example, you can create a Binding that formats a number before saving, or a Binding that syncs the value with a remote server on every change.

swift
struct ValidatedField: View {
    @State private var email: String = ""

    var emailBinding: Binding<String> {
        .init(
            get: { email },
            set: { email = $0.lowercased().trimmingCharacters(in: .whitespaces) }
        )
    }

    var body: some View {
        TextField("Email", text: emailBinding)
    }
}

In the listing, the custom emailBinding automatically converts text to lowercase and trims whitespace on every change. The TextField uses this Binding instead of directly binding to $email. This approach centralizes validation and data transformation inside the Binding without cluttering the code with onChange handlers.

Errors and Anti-Patterns with @Binding

The first and most common mistake is passing a value instead of a Binding. If a child component declares @Binding var text: String, and the parent passes text (without $), the compiler will give an error: Cannot convert value of type 'String' to expected argument type 'Binding<String>'. The solution — always use the $ prefix when passing: $text.

The second mistake — Binding on read-only data. If the child view only needs to read a value, do not use @Binding — a simple let or @State from the parent is sufficient. Binding implies the ability to write, and excess write permissions complicate debugging and violate the principle of least privilege.

The third issue — Binding.constant in production. Binding.constant(value) creates a dummy binding without feedback — changes are ignored. Use constant only for prototyping and previews (Xcode Previews), but never in real code. For tests, use Binding(get:set:) with controlled behavior.

Frequently Asked Questions

What is the difference between @Binding and @State?

@State owns the data and manages its storage in the heap. @Binding only references existing state without ownership. @State is always private, @Binding is an input parameter of the child view.

Can a Binding be created without @State?

Yes, through the Binding(get:set:) initializer or Binding.constant(value). Binding can also be obtained from @StateObject via the $object.$property projection and from Publisher via Binding(get:set:) inside Subscribe.

How to pass Binding through multiple nesting levels?

@Binding is passed through a chain: each intermediate component declares @Binding and passes it further via $. All levels reference the same data source in the root view.

Why doesn't Binding.constant update the interface?

Binding.constant creates a dummy wrapper — the setter ignores new values. It is intended only for prototyping and SwiftUI Previews where feedback from the child component is not required.

Can @Binding be optional?

Yes, Binding<T?> is supported. If you pass Binding<String?>, the child view will be able to set nil. This is convenient for optional form fields or states with a reset option.

Summary

  • @Binding — Property Wrapper for two-way binding with parent view data
  • Does not own data — only provides access to the source of truth
  • $ Projection turns @State, @StateObject into Binding for passing to child views
  • Custom Binding created via Binding(get:set:) with additional logic
  • Binding.constant — only for previews and prototypes
  • Multi-level passing — Binding passes through any nesting depth
  • Alternative to callbacks — declarative way to modify state from child components

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also