@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 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.
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.
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.
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 | @Binding | Callbacks |
|---|---|---|
| Code | One annotation + $ | Closure + invocation |
| Multi-level | Automatic | Closure chain |
| Testing | Binding(value:constant) | Mock closures |
| Readability | High | Medium |
| Flexibility | Data only | Any 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 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.
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.
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.
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.
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
@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.
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.
@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.
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.
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
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.
Read also