@Binding vs @Bindable - SwiftUI

@Binding allows a child view to modify a parent view's state, enabling two-way data binding between views. @Bindable, introduced with SwiftData, automatically exposes properties of an observable model for binding, simplifying data handling in models.

@Binding vs @Bindable - SwiftUI

Let’s talk about something that often trips up newcomers to SwiftUI: the difference between @Binding and @Bindable. Both are incredibly useful tools, but knowing when and how to use them can be tricky. If you're like me, it probably took a couple of real-world projects before you fully understood the distinction. So, let’s break it down with some examples and hard-earned wisdom.

TL;DR When to Use Which:

  • Use @Binding when you’re creating reusable SwiftUI views and need to share state between parent and child views.
  • Use @Bindable when you’re working with data models in SwiftData and want to automatically expose their properties for SwiftUI bindings.

In SwiftUI, both @Binding and @Bindable are property wrappers used to manage data flow, but they serve different purposes and apply in distinct contexts. Here's a breakdown of the differences:

@Binding: A Two-Way Data Connection

  • Purpose: @Binding creates a connection between two views, allowing a child view to modify data owned by a parent view.
  • Use Case: It’s typically used when a child view needs to modify a value from the parent without directly owning the state. The parent view passes a binding (reference) to its state, and the child can both read and write to that value.

@Binding Key Points:

  1. @Binding doesn’t hold the data; it only reflects and modifies the data from the parent.
  2. @Binding allows for two-way data binding between parent and child.
  3. Typically used in reusable components like forms, custom buttons, etc., where the child doesn’t own the data but can modify it.

@Bindable: New in SwiftData for Observing Properties

  • Purpose: @Bindable is part of SwiftData (introduced in Swift 5.9), and it simplifies creating bindings for properties in model objects that conform to Observable by automatically exposing bindings for each observable property.
  • Use Case: It’s used when you need to bind to properties of a data model that conforms to the Observable protocol without manually declaring @Published properties. It's more of a model-level abstraction for property bindings rather than focusing on parent-child view communication like @Binding.

@Bindable Key Points:

  1. @Bindable simplifies binding for model objects by exposing properties for binding automatically.
  2. @Bindable is part of SwiftData and is useful when you want to bind multiple properties without explicitly defining @Published in your model.

The Basics: What Are @Binding and @Bindable?

Before we dive into the nitty-gritty, let’s clarify what these property wrappers are and what they’re supposed to do.

  • @Binding: This is a two-way connection between a property in one view and another view that’s modifying it. Think of it like a "bridge" that allows data to flow back and forth.
  • @Bindable: A newer property wrapper introduced in SwiftUI that works with types conforming to the Observable protocol. It allows views to modify an observable object’s property directly while keeping the state management centralized.

At first glance, they might look similar since they both deal with passing data between views, but they serve very different roles, especially in how they fit into the larger architecture of your app.

Core Differences:

Aspect@Binding@Bindable
Primary FunctionTwo-way data binding between viewsExposes properties of an observable model for binding
Typical Use CaseParent-child view communicationData models that conform to Observable
ScopeLocal to SwiftUI viewsBroad, model-based (SwiftData)
ExampleToggles, forms in child viewsBinding to model properties without manual @Published
Introduced InSwiftUI (since its launch)SwiftData (from Swift 5.9)

Understanding @Binding with code sample

Let me take you back to one of my earlier SwiftUI projects—a simple expense tracker app. I was building a form where users could enter a new expense, and I wanted to pass data from that form back to the main list. Simple enough, right?

I used @State to manage the form’s input, but when I wanted that input to modify data in a parent view, @Binding came to the rescue.

struct ExpenseInputView: View {
    @Binding var amount: Double

    var body: some View {
        TextField("Enter amount", value: $amount, format: .number)
    }
}

In this example, the @Binding allows the child view (ExpenseInputView) to directly modify the amount property that’s defined in the parent. It creates a two-way connection, so any changes made in the child view reflect immediately in the parent.

Using @Binding in SwiftUI Code:

Imagine you're building a form where a toggle switch in a child view controls a boolean value (e.g., enabling notifications) owned by the parent view:

struct ParentView: View {
    @State private var isEnabled = false
    
    var body: some View {
        ToggleView(isEnabled: $isEnabled)  // Pass the binding
    }
}

struct ToggleView: View {
    @Binding var isEnabled: Bool  // Use @Binding to connect to parent’s state
    
    var body: some View {
        Toggle("Enable Notifications", isOn: $isEnabled)
    }
}

@Binding is perfect for passing down data that needs to be edited. But remember, it’s only for primitive data—simple, single properties like numbers, strings, or booleans. When you start dealing with more complex data types, especially custom objects, @Binding can become unwieldy. That’s when you might want to look at something like @Bindable.

Understanding @Bindable with code sample

Fast forward a bit, and I’m working on a more complex app—let’s say a social app where users can update their profile info. The user’s data was stored in an ObservableObject called UserProfile. I needed a way for multiple views to modify properties like the username and bio directly, but also wanted all those changes to propagate cleanly throughout the app.

That’s when @Bindable came into play.

Using @Bindable in SwiftUI Code:

@Observable
class UserProfile {
    var username: String = ""
    var bio: String = ""
}

struct EditProfileView: View {
    @Bindable var profile: UserProfile

    var body: some View {
        Form {
            TextField("Username", text: $profile.username)
            TextField("Bio", text: $profile.bio)
        }
    }
}

In this case, @Bindable works with the Observable object UserProfile. Instead of using @Binding for each individual property, @Bindable allows you to modify the ObservableObject's properties directly. This is super handy when you're dealing with more complex data structures because it keeps your code clean and centralizes state management.

If you’ve ever found yourself passing around multiple @Binding properties for a single object, you’ve probably felt the pain of keeping everything in sync. That’s a good sign you should be using @Bindable instead. It’s great for keeping your views connected to complex models without needing to micromanage every little property.

When to Use @Binding

  • Simple State: If you’re working with primitive data types like Int, String, Bool, or Double, and you just need to pass them between parent and child views.
  • Short Lifespan: If the data is only being used temporarily in a specific view or group of views, @Binding is a clean, straightforward solution.

In one of my earlier apps, a timer app, I used @Binding to pass the duration from the parent to a slider in a settings view. The user could change the duration and the parent view updated instantly. Perfect use case for @Binding.

When to Use @Bindable

  • Complex Data: When you’re dealing with objects that have multiple properties, or if you’re working with a model that conforms to Observable, @Bindable is your friend.
  • Longer Lifespan: If the data needs to be shared across multiple views and needs centralized management, @Bindable simplifies everything. You won’t need to pass individual @Binding properties everywhere, keeping your code more maintainable.

In a health tracking app I built, the user’s health data was stored in a HealthProfile object. Rather than pass around a separate @Binding for weight, height, age, and activity level, I used @Bindable to manage everything in one place. It saved me a lot of headaches down the line when adding new features.

Lessons Learned

1. The Over-binding Trap

I remember in one app, I started with just a couple of @Binding properties. But as the project grew, I found myself passing half a dozen bindings to child views. It became a tangled mess. I had to refactor the code and use @Bindable for the more complex state. My advice: if you’re passing more than two or three bindings, rethink your strategy.

2. Performance Pitfalls

When you’re dealing with deeply nested views, bindings can start to affect performance, especially when there’s a lot of data flowing between views. This is where @Bindable can help, because it keeps state changes more organized and reduces unnecessary updates.

3. State Ownership

One mistake I often see—and I’ve been guilty of it myself—is using @Binding when you really should be using @State or @ObservedObject. Remember, @Binding is meant for passing data down, not for keeping track of your app’s core state. I’ve seen junior developers (and sometimes even myself on a bad day) try to manage too much state with bindings, leading to confusing bugs and hard-to-trace issues.

Advice for your SwiftUI Roadmap

In my years of working with SwiftUI, one thing has become clear: knowing when to use @Binding versus @Bindable is a game-changer. It’s all about context. If you’re building a simple, small-scale app with basic state management, @Binding might be all you need. But once your app scales—whether in complexity or data—@Bindable will save you from a world of headaches.

So, the next time you’re wondering how to pass data between views, stop and ask yourself: is this a simple, temporary state, or is it part of a bigger, more complex picture? Your answer will lead you to the right choice, and your future self will thank you.

Happy coding! And remember: always respect the data flow.