@Binding in SwiftUI

abdul ahad
4 min readOct 2, 2024
Photo by Sai Abhinivesh Burla on Unsplash

Bindings in SwiftUI allow you to establish a two-way connection between a view and its data source. This ensures that any changes made to the data are reflected in the view, and vice versa, keeping the app’s state consistent. Let’s break down the concept with examples:

Core Concept: Single Source of Truth

In SwiftUI, state should have a single source of truth to avoid inconsistencies. A view might need to read and modify this state without knowing where the state comes from. For this, bindings are used to connect the state to the UI.

Example 1: Simple Counter with @Binding

Using @State:

Here’s a counter that uses @State:

struct ContentView: View {
@State private var count = 0

var body: some View {
Button("Increment: \(count)") {
count += 1
}
}
}
  • The @State property count holds the state inside the view, and the button increments it.

Refactoring with @Binding:

Now, we want the Counter view to take an external state (a value passed from another view). To achieve this, we replace @State with @Binding:

struct Counter: View {
@Binding var value: Int

var body: some View {
Button("Increment: \(value)") {
value += 1
}
}
}
  • The Counter view doesn’t own the state anymore—it only modifies the state through the binding.

How to Use @Binding:

In the parent view (e.g., ContentView), we pass the state to the Counter view using a binding:

struct ContentView: View {
@State private var count = 0

var body: some View {
Counter(value: $count) // $ creates a binding
}
}
  • Here, the $count creates a binding to the @State property count, which can now be modified by the Counter view.

Manual Implementation Without @Binding

If we didn’t have @Binding, we would manually create a getter and setter for the external state:

struct Counter: View {
var value: Int
var setValue: (Int) -> Void

var body: some View {
Button("Increment: \(value)") {
setValue(value + 1)
}
}
}

struct ContentView: View {
@State private var count = 0

var body: some View {
Counter(value: count, setValue: { newValue in count = newValue })
}
}

In this approach, the Counter view gets value as a normal property and modifies it using the setValue function. This pattern mirrors how a binding works behind the scenes: a value with a getter and setter.

Simplified with Binding Type

We can simplify this pattern using the Binding type directly, which combines the getter and setter into one property:

struct Counter: View {
var value: Binding<Int>

var body: some View {
Button("Increment: \(value.wrappedValue)") {
value.wrappedValue += 1
}
}
}

Here, value.wrappedValue accesses the current value and modifies it.

Property Wrappers: @State and @Binding

To make the code cleaner, SwiftUI provides @State and @Binding property wrappers. Using @Binding generates code similar to the manual binding setup, without needing to write the getter and setter explicitly.

$value Syntax: Property Wrapper Projection

When you write $count, it is shorthand for accessing the projected value of a @State or @Binding property. Behind the scenes, SwiftUI converts this: For more details read propertyWrappers.

Counter(value: $count)

To this:

Counter(value: _count.projectedValue)

This projectedValue gives access to a binding to the underlying state. This projection mechanism is built into Swift's property wrappers.

Binding to Other Sources: @StateObject and @ObservedObject

The $ syntax works for more than just @State. You can bind to @StateObject and @ObservedObject as well. For instance:


final class Model: ObservableObject {
@Published var value = 0
}

struct ContentView: View {
@StateObject var model = Model()

var body: some View {
Counter(value: $model.value) // Binding to model's value
}
}

In this case, the Counter is binding to a property (value) from a model object.

Advanced Example: Computed Property Binding

You can even bind to computed properties, as long as they have a getter and a setter:

final class Model: ObservableObject {
@Published var value = 0

var clampedValue: Int {
get { min(max(0, value), 10) }
set { value = newValue }
}
}

struct ContentView: View {
@StateObject var model = Model()

var body: some View {
Counter(value: $model.clampedValue) // Binding to a computed property
}
}

Here, the Counter is bound to clampedValue, a computed property that restricts the value between 0 and 10.

Summary of Key Concepts:

  • @Binding allows passing state from a parent view to a child view, enabling two-way data flow.
  • $value is shorthand for accessing the binding to a state or observable property.
  • Binding type wraps the getter and setter for a value, which simplifies managing state.
  • You can manually construct bindings using Binding(get:set:), but it's easier with property wrappers.

Bindings help keep your SwiftUI app’s state consistent by providing an easy mechanism to share and modify values across views.

--

--

abdul ahad
abdul ahad

Written by abdul ahad

A software developer dreaming to reach the top and also passionate about sports and language learning