Swiftyn LogoSwiftyn
LearnInterview PrepRoadmapsArchitect Profile
Swift LanguageSwiftUIUIKitiOS ConceptsmacOS

SwiftUI Topics

Introduction to SwiftUISwiftUI App LifecycleViews and ModifiersTextImageShapesSF SymbolsButtonsStacksSpacerDividerScrollViewGroupsSectionsHStackVStackZStackLazyVStackLazyHStackLazyVGridLazyHGridGridGridRowGeometryReaderSafeAreaFrames and AlignmentPaddingOverlay and BackgroundPreferenceKeyAnchor PreferencesCustom Layouts@State@Binding@ObservedObject@StateObject@EnvironmentObject@Environment@PublishedObservableObject@Observable@BindableData Flow in SwiftUITwo Way BindingNavigationStackNavigationSplitViewNavigationPathProgrammatic NavigationTabViewDeep LinkingSheetFullScreenCoverPopoverAlertsCustom AnimationsAsync Await in SwiftUIUIKit Integration
Browse SwiftUI Topics
Introduction to SwiftUISwiftUI App LifecycleViews and ModifiersTextImageShapesSF SymbolsButtonsStacksSpacerDividerScrollViewGroupsSectionsHStackVStackZStackLazyVStackLazyHStackLazyVGridLazyHGridGridGridRowGeometryReaderSafeAreaFrames and AlignmentPaddingOverlay and BackgroundPreferenceKeyAnchor PreferencesCustom Layouts@State@Binding@ObservedObject@StateObject@EnvironmentObject@Environment@PublishedObservableObject@Observable@BindableData Flow in SwiftUITwo Way BindingNavigationStackNavigationSplitViewNavigationPathProgrammatic NavigationTabViewDeep LinkingSheetFullScreenCoverPopoverAlertsCustom AnimationsAsync Await in SwiftUIUIKit Integration
Swiftyn Logo

Swiftyn

The go-to platform for Apple developers. Swift, SwiftUI, and beyond.

Questions? Email us at support@swe180.com

Categories

  • SwiftUI
  • Swift Language
  • Xcode
  • visionOS

Our Products

  • SWE180
  • One Percent Engineer

Resources

  • About
  • RSS Feed
  • Apple Developer

© 2026 Swiftyn. All rights reserved.

Privacy PolicyTerms of Service

Swiftyn is the premier learning platform and developer resource for mastering the Apple ecosystem. Whether you are an aspiring iOS developer looking to learn Swift 6, a macOS engineer diving into advanced system architecture, or an XR pioneer building the future with visionOS, our beautifully crafted tutorials, roadmaps, and interview prep guides have you covered. Built by Apple developers, for Apple developers.

SwiftUI8 min read

Mastering @Bindable in SwiftUI: Seamless Data Flow for Observable Objects

@Bindable is a powerful new property wrapper introduced in SwiftUI to simplify two-way data binding with `@Observable` objects. It allows you to create bindings to properties of an observed object directly, making your views more reactive and your code cleaner. This article dives deep into `Bindable`'s usage, advantages, and best practices.

Introduction: The Evolution of SwiftUI Data Flow

SwiftUI's data flow mechanisms have continuously evolved, aiming for greater simplicity and clarity. With the introduction of the @Observable macro in iOS 17 and macOS 14, Apple provided a more performant and less verbose way to manage observable objects compared to the traditional ObservableObject protocol and @Published property wrapper.

However, @Observable alone doesn't directly offer the two-way binding capabilities that @State or @Binding provide for value types or properties of ObservableObject. This is where @Bindable steps in. @Bindable is a complementary property wrapper that allows you to create elegant, two-way bindings to properties within an @Observable object, ensuring that any changes to those properties automatically reflect in your UI and vice-versa.

Prior to @Bindable, achieving two-way binding with properties of a class conforming to ObservableObject often involved passing @Bindings explicitly or relying on $ syntax with @ObservedObject or @StateObject, which could become cumbersome with nested objects. @Bindable simplifies this significantly, especially in the context of the new @Observable macro.

This article will guide you through the intricacies of @Bindable, demonstrating how it integrates with @Observable to create a robust and highly reactive data flow within your SwiftUI applications. We'll explore its benefits, typical use cases, and how to effectively incorporate it into your projects.

Understanding @Bindable and Its Role

At its core, @Bindable facilitates the creation of Binding instances from properties of an @Observable object. When you declare a property or a local variable with @Bindable, you're essentially telling SwiftUI: "I want to be able to create two-way bindings ($ prefix) directly to the properties of this specific instance of an @Observable type."

Consider a scenario where you have an Observable model object, and one of its properties needs to be edited by a TextField. Without @Bindable, you'd typically have to create a separate @State for the TextField's value and then update the model property manually on change, or find a less direct way to bind. @Bindable eliminates this boilerplate.

Why use @Bindable with @Observable?

  1. Simplicity: It provides a direct, concise syntax for creating bindings, much like how $ works with @State or @Binding itself.
  2. Two-way Data Flow: Allows changes made in a child view (e.g., via a TextField or Toggle) to directly update the underlying property of the @Observable object, and vice versa.
  3. Performance: Leverages the efficient change tracking of the @Observable macro, ensuring that only the relevant parts of your UI are re-rendered when a property changes.
  4. Cleaner Code: Reduces the need for manual Binding creation or intermediate @State properties, making your view code more readable and maintainable.

It's important to remember that @Bindable primarily works with instances of types marked with @Observable. While @State, @Binding, and @ObservedObject work with ObservableObject, @Bindable is the correct (and often only) way to get seamless two-way binding for @Observable types. You will typically see @Bindable used when passing an @Observable object into a child view or within a view to bind to its properties.

Basic Usage: Working with @Bindable

Let's dive into a practical example to see how @Bindable works. We'll define a simple Settings class marked with @Observable and then create a SwiftUI view to display and modify its properties.

First, define your observable data model:

swift
import Foundation
import Observation // Import the Observation framework

@Observable
class Settings {
    var username: String
    var enableNotifications: Bool
    var volume: Double

    init(username: String, enableNotifications: Bool, volume: Double) {
        self.username = username
        self.enableNotifications = enableNotifications
        self.volume = volume
    }
}

Now, let's create a view that uses this Settings object. We'll pass the Settings object into a child view using @Bindable.

swift
import SwiftUI

struct SettingsView: View {
    // Use @State for the root source of truth in the parent view
    @State private var userSettings = Settings(username: "Guest", enableNotifications: true, volume: 0.75)

    var body: some View {
        NavigationView {
            Form {
                Text("Current Username: \(userSettings.username)")
                
                // Pass the bindable settings object to a child view
                Section("User Preferences") {
                    EditUserSettingsView(settings: userSettings)
                }

                Section("Raw Settings") {
                    VStack(alignment: .leading) {
                        Text("Name: \(userSettings.username)")
                        Text("Notifications: \(userSettings.enableNotifications ? "On" : "Off")")
                        Text("Volume: \(String(format: "%.0f%%", userSettings.volume * 100))")
                    }
                }
            }
            .navigationTitle("App Settings")
        }
    }
}

struct EditUserSettingsView: View {
    // Declare the incoming observable object as @Bindable
    @Bindable var settings: Settings

    var body: some View {
        VStack {
            TextField("Username", text: $settings.username)
                .textFieldStyle(.roundedBorder)
                .padding(.vertical, 5)

            Toggle("Enable Notifications", isOn: $settings.enableNotifications)
                .padding(.vertical, 5)

            HStack {
                Text("Volume")
                Slider(value: $settings.volume, in: 0...1)
            }
            .padding(.vertical, 5)
        }
        .padding()

        // Simulate preview behavior
        #if DEBUG
        .onAppear {
            print("EditUserSettingsView appeared. Initial username: \(settings.username)")
        }
        .onChange(of: settings.username) { oldVal, newVal in
            print("Username changed from \(oldVal) to \(newVal)")
        }
        #endif
    }
}

// To run in a preview:
struct SettingsView_Previews: PreviewProvider {
    static var previews: some View {
        SettingsView()
    }
}

In this example:

  1. Settings is declared with @Observable.
  2. SettingsView uses @State to own an instance of Settings, establishing it as the source of truth.
  3. EditUserSettingsView takes the Settings instance using @Bindable var settings: Settings. This is the crucial part. By marking settings as @Bindable, we can now use the $ prefix directly on its properties (e.g., $settings.username, $settings.volume) to create two-way bindings for TextField, Toggle, and Slider.

When you interact with the UI elements in EditUserSettingsView, the userSettings object in SettingsView is directly updated, and SettingsView (and any other view observing userSettings) will react accordingly, thanks to the @Observable macro's automatic change propagation. This offers an incredibly clean and efficient way to manage data flow.

swift
import Foundation
import Observation

@Observable
class Settings {
    var username: String
    var enableNotifications: Bool
    var volume: Double

    init(username: String, enableNotifications: Bool, volume: Double) {
        self.username = username
        self.enableNotifications = enableNotifications
        self.volume = volume
    }
}
swift
import SwiftUI

struct SettingsView: View {
    @State private var userSettings = Settings(username: "Guest", enableNotifications: true, volume: 0.75)

    var body: some View {
        NavigationView {
            Form {
                Text("Current Username: \(userSettings.username)")
                
                Section("User Preferences") {
                    EditUserSettingsView(settings: userSettings)
                }

                Section("Raw Settings") {
                    VStack(alignment: .leading) {
                        Text("Name: \(userSettings.username)")
                        Text("Notifications: \(userSettings.enableNotifications ? "On" : "Off")")
                        Text("Volume: \(String(format: "%.0f%%", userSettings.volume * 100))")
                    }
                }
            }
            .navigationTitle("App Settings")
        }
    }
}

struct EditUserSettingsView: View {
    @Bindable var settings: Settings

    var body: some View {
        VStack {
            TextField("Username", text: $settings.username)
                .textFieldStyle(.roundedBorder)
                .padding(.vertical, 5)

            Toggle("Enable Notifications", isOn: $settings.enableNotifications)
                .padding(.vertical, 5)

            HStack {
                Text("Volume")
                Slider(value: $settings.volume, in: 0...1)
            }
            .padding(.vertical, 5)
        }
        .padding()
    }
}

struct SettingsView_Previews: PreviewProvider {
    static var previews: some View {
        SettingsView()
    }
}

Advanced Scenarios: Nested Observables and Collections

@Bindable truly shines when dealing with more complex data structures, such as nested @Observable objects or collections of them. Let's consider a scenario where our Settings object contains an array of UserAccount objects, each also being @Observable.

First, define the UserAccount model:

swift
import Foundation
import Observation

@Observable
class UserAccount: Identifiable {
    let id = UUID()
    var username: String
    var email: String
    var isActive: Bool

    init(username: String, email: String, isActive: Bool) {
        self.username = username
        self.email = email
        self.isActive = isActive
    }
}

@Observable
class AppData {
    var currentUser: UserAccount
    var allAccounts: [UserAccount]

    init() {
        self.currentUser = UserAccount(username: "Admin", email: "admin@example.com", isActive: true)
        self.allAccounts = [
            self.currentUser,
            UserAccount(username: "JaneDoe", email: "jane@example.com", isActive: false),
            UserAccount(username: "JohnSmith", email: "john@example.com", isActive: true)
        ]
    }
}

Now, let's create a view that manages these accounts. You'll typically own the root AppData object with @State in your top-level view.

swift
import SwiftUI

struct AccountManagerView: View {
    @State private var appData = AppData()

    var body: some View {
        NavigationView {
            Form {
                Section("Current User") {
                    // Pass the bindable current user to a detail view
                    CurrentUserDetailView(user: appData.currentUser)
                }

                Section("All Accounts") {
                    ForEach(appData.allAccounts) { account in
                        // ForEach and @Bindable work seamlessly together
                        // The $ syntax on 'account' implicitly creates a Bindable
                        // for each element within the ForEach block.
                        AccountRowView(account: account)
                    }
                }
            }
            .navigationTitle("Account Manager")
        }
    }
}

struct CurrentUserDetailView: View {
    @Bindable var user: UserAccount

    var body: some View {
        VStack(alignment: .leading) {
            TextField("Username", text: $user.username)
                .textFieldStyle(.roundedBorder)
            TextField("Email", text: $user.email)
                .textFieldStyle(.roundedBorder)
            Toggle("Active", isOn: $user.isActive)
        }
        .padding(.vertical, 5)
        .navigationTitle(user.username)
    }
}

struct AccountRowView: View {
    @Bindable var account: UserAccount

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(account.username).font(.headline)
                Text(account.email).font(.subheadline)
            }
            Spacer()
            Toggle("Active", isOn: $account.isActive)
                .labelsHidden()
        }
    }
}

struct AccountManagerView_Previews: PreviewProvider {
    static var previews: some View {
        AccountManagerView()
    }
}

Key takeaways from this advanced example:

  1. Nested @Observable: Both AppData and UserAccount are @Observable. When AppData is updated, SwiftUI will correctly track changes within its currentUser or elements of allAccounts.
  2. ForEach and @Bindable: When iterating over a collection of @Observable objects (like appData.allAccounts) in a ForEach loop, you typically pass each account directly. Within the body of ForEach, SwiftUI understands that account is an @Observable and allows you to use the $ prefix on its properties implicitly. You don't need to explicitly declare @Bindable within the ForEach body itself; the binding context is established by the loop and the @Bindable declaration in the AccountRowView.
  3. Two-way Binding: Modifying user.username in CurrentUserDetailView or account.isActive in AccountRowView directly updates the corresponding UserAccount object within AppData, triggering re-renders only where necessary. This provides a highly efficient and reactive UI.
swift
import Foundation
import Observation

@Observable
class UserAccount: Identifiable {
    let id = UUID()
    var username: String
    var email: String
    var isActive: Bool

    init(username: String, email: String, isActive: Bool) {
        self.username = username
        self.email = email
        self.isActive = isActive
    }
}

@Observable
class AppData {
    var currentUser: UserAccount
    var allAccounts: [UserAccount]

    init() {
        self.currentUser = UserAccount(username: "Admin", email: "admin@example.com", isActive: true)
        self.allAccounts = [
            self.currentUser,
            UserAccount(username: "JaneDoe", email: "jane@example.com", isActive: false),
            UserAccount(username: "JohnSmith", email: "john@example.com", isActive: true)
        ]
    }
}
swift
import SwiftUI

struct AccountManagerView: View {
    @State private var appData = AppData()

    var body: some View {
        NavigationView {
            Form {
                Section("Current User") {
                    CurrentUserDetailView(user: appData.currentUser)
                }

                Section("All Accounts") {
                    // Iterating over bindable objects directly within a ForEach
                    ForEach(appData.allAccounts) { account in
                        AccountRowView(account: account)
                    }
                }
            }
            .navigationTitle("Account Manager")
        }
    }
}

struct CurrentUserDetailView: View {
    @Bindable var user: UserAccount // The @Bindable declaration

    var body: some View {
        VStack(alignment: .leading) {
            TextField("Username", text: $user.username)
                .textFieldStyle(.roundedBorder)
            TextField("Email", text: $user.email)
                .textFieldStyle(.roundedBorder)
            Toggle("Active", isOn: $user.isActive)
        }
        .padding(.vertical, 5)
        .navigationTitle(user.username) // This also reacts to username changes
    }
}

struct AccountRowView: View {
    @Bindable var account: UserAccount // The @Bindable declaration

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(account.username).font(.headline)
                Text(account.email).font(.subheadline)
            }
            Spacer()
            Toggle("Active", isOn: $account.isActive)
                .labelsHidden()
        }
    }
}

struct AccountManagerView_Previews: PreviewProvider {
    static var previews: some View {
        AccountManagerView()
    }
}

Best Practices and Considerations

While @Bindable is incredibly powerful, understanding its nuances and adhering to best practices will help you build robust and performant SwiftUI applications.

When to use @Bindable?

  • When you need two-way binding to properties of an @Observable object. This is its primary purpose, especially for UI controls like TextField, Toggle, Slider, etc.
  • When passing an @Observable object down a view hierarchy. Declare the incoming parameter (var or let) as @Bindable in the child view if you intend to modify its properties directly or pass bindings to its properties further down.
  • Locally within a view: You can use @Bindable for local variables or properties within a view if you temporarily need to create bindings to an @Observable instance that you've received through other means (e.g., from an @EnvironmentObject).

When not to use @Bindable?

  • When the object isn't @Observable: @Bindable is specifically designed for types marked with the @Observable macro. For ObservableObject types, stick to @ObservedObject or @StateObject and @Binding for two-way interactions.
  • When you only need to read properties: If a child view only needs to display data from an @Observable object and doesn't need to modify it or create bindings to it, simply pass the object directly without @Bindable. SwiftUI's observation system will still ensure the view updates when relevant properties change.
  • For value types (struct): @Bindable targets reference types (class) tagged with @Observable. For structs, you would use @State for local ownership and @Binding for two-way passing.

Important Considerations:

  • Source of Truth: Remember that @Bindable itself doesn't establish an object as a source of truth. It merely creates bindings to an already existing source of truth (typically owned by @State in an ancestor view, or provided via @Environment).
  • Performance: The @Observable macro and @Bindable work together to provide highly granular updates. SwiftUI re-renders only the views that actually depend on the changed property. This is a significant improvement over ObservableObject where a change to any @Published property could potentially re-render a much larger portion of the view hierarchy.
  • Immutability vs. Mutability: While @Bindable allows you to mutate properties of a reference type, consider whether the mutation should happen directly in the view or be delegated to methods within the model. For complex logic, it's often better to have your model object encapsulate the modification logic.
  • Concurrency: When modifying @Observable objects from background threads, ensure proper synchronization if multiple threads can access the same object. While SwiftUI's observation system is robust, race conditions can still occur if not managed carefully in your model layer. Always perform UI updates on the main thread.

By following these guidelines, you can leverage @Bindable to create elegant, efficient, and maintainable SwiftUI applications.

Compatibility

@Bindable and the @Observable macro are features introduced in iOS 17, macOS 14, tvOS 17, and watchOS 10. To use these new property wrappers and macros, your project must target these minimum deployment versions or higher.

If you need to support earlier versions of Apple's operating systems, you will have to continue using ObservableObject and @Published for your reference-type data models. The transition from ObservableObject to @Observable is often straightforward, primarily involving replacing ObservableObject and @Published with the @Observable macro. However, the data flow mechanisms, particularly for two-way binding, change significantly as demonstrated by the introduction of @Bindable.

Clunky Two-Way Binding with @Observable

Becoming a stronger iOS Engineer

THE PROBLEM: Clunky Two-Way Binding with @Observable

Before @Bindable, creating two-way bindings to properties of an @Observable object often required manual Binding initializers or using @State for intermediate values, leading to verbosity and less elegant code. Directly using `$` on a plain @Observable object wasn't possible for two-way binding.

swift
// Problematic approach (pre-@Bindable or without it)
struct MyView: View {
    var settings: Settings // No @Bindable

    var body: some View {
        // Error: Cannot convert value of type 'Binding<String>' to expected argument type 'String'
        // TextField("Name", text: $settings.name)

        // Could use: Binding(get: { settings.name }, set: { settings.name = $0 })
        // But this is verbose.
    }
}

WHAT HAPPENS INTERNALLY?

@Bindable is a lightweight property wrapper that provides syntactic sugar over creating a Binding<Value> for properties of an @Observable instance. It interacts with the Observation framework to ensure efficient change tracking.

View Hierarchy
@State (root @Observable object)
Child View with @Bindable
UI Control (TextField, Toggle)
1

1. @Observable Tracking

The @Observable macro transforms your class, injecting code that tracks access to its properties. When a property is read, SwiftUI registers that dependency. When it's written, it notifies observers.

2

2. @Bindable Declaration

When you declare `@Bindable var myObject: MyObservableType`, SwiftUI understands that `myObject` is an @Observable instance whose properties might need two-way bindings.

3

3. '$' Operator Magic

Using `$myObject.someProperty` with `@Bindable` automatically generates a `Binding<TypeOfSomeProperty>` that reads from and writes back to `myObject.someProperty`, leveraging the underlying observation system.

4

4. UI Control Integration

UI controls like `TextField`, `Toggle`, and `Slider` expect a `Binding` as their primary value source. `@Bindable` fulfills this requirement seamlessly via the `$` syntax.

Visualized execution hierarchy.

Powerful Guarantees

Automatic Binding Creation

Provides direct '$' prefix access for two-way bindings to properties of @Observable objects.

Reactive UI Updates

Changes from UI controls flow back to the @Observable object, triggering efficient, targeted view updates.

Granular Observation

Leverages the @Observable macro's fine-grained observation, ensuring only relevant views re-render.

Code Readability

Significantly cleans up data flow code compared to manual Binding creation or intermediate @State.

REAL PRODUCTION EXAMPLE: A Complex Form with Nested Data

Imagine a user profile editing screen where an `UserProfile` object contains nested `Address` and `ContactInfo` objects, all of which are `@Observable`. Without `@Bindable`, every field would need manual `Binding` setup or `@State` for intermediate values, making the form cumbersome to build and maintain.

Impact / Results
Cleaner form code with direct bindings
Reduced boilerplate for complex data structures
Reliable two-way updates across nested models
THE FIX: Using @Bindable for Nested Observable Properties
swift
import SwiftUI
import Observation

@Observable
class Address {
    var street: String = ""
    var city: String = ""
}

@Observable
class UserProfile {
    var name: String = ""
    var email: String = ""
    var address = Address()
}

struct ProfileEditorView: View {
    @State private var profile = UserProfile()

    var body: some View {
        Form {
            TextField("Name", text: $profile.name) // Direct binding to top-level property
            TextField("Email", text: $profile.email)
            
            // Pass the entire profile object (which is @Observable) to a sub-view
            // Sub-view declares @Bindable var profile: UserProfile
            AddressEditorView(userProfile: profile)
        }
    }
}

struct AddressEditorView: View {
    @Bindable var userProfile: UserProfile // Declare as @Bindable

    var body: some View {
        Section("Address") {
            // Now you can bind directly to nested properties
            TextField("Street", text: $userProfile.address.street)
            TextField("City", text: $userProfile.address.city)
        }
    }
}

INTERVIEW PERSPECTIVE

Common Question

“Explain the role of `@Bindable` in the context of the new `@Observable` macro in SwiftUI.”

Strong Answer

`@Bindable` is a key property wrapper for the new `@Observable` macro (iOS 17+). While `@Observable` makes a class observable and triggers view updates when its properties change, it doesn't directly provide two-way bindings (`$`). `@Bindable` fills this gap by allowing you to take an instance of an `@Observable` object and then use the `$` prefix on its properties (e.g., `$myObject.property`) to create two-way bindings that SwiftUI controls expect. This ensures that UI changes propagate back to the underlying `@Observable` model, and model changes update the UI, all with efficient, fine-grained observation.

Interviewers Expect you to understand:
  • Knowledge of `@Observable` benefits (fine-grained updates)
  • Understanding `$` syntax for bindings
  • Distinction between `@Bindable` and `@ObservedObject`
  • Situational use: passing `@Observable` to child views for modification
KEY TAKEAWAY

Use `@Bindable` whenever you need two-way data binding to properties of an `@Observable` object. It simplifies your code by allowing direct `$` access, making your SwiftUI data flow cleaner, more reactive, and efficient, especially with nested and complex data structures. Always remember: `@State` owns the `@Observable` object, `@Bindable` binds *to* its properties.

Common Interview Questions

What is the difference between `@Bindable` and `@ObservedObject`?

`@ObservedObject` is used with types conforming to `ObservableObject` (the older protocol) to cause a view to re-render when the object changes. `@Bindable` is used with types marked with the `@Observable` macro (the newer system introduced in iOS 17) specifically to create two-way bindings (`$`) to the properties *of* that observable object.

Can I use `@Bindable` with a `struct`?

No, `@Bindable` is designed specifically for reference types (classes) that are marked with the `@Observable` macro. For value types (`struct`s), you would typically use `@State` to own the data and `@Binding` to pass two-way editable access to child views.

Does `@Bindable` make a copy of the object?

No, `@Bindable` (like `@ObservedObject` or plain object passing) does not create a copy of the object. It holds a reference to the original `@Observable` instance, allowing direct manipulation of its properties and ensuring that all views observing that instance see the same, up-to-date data.

When should I use `@State` versus `@Bindable` for an `@Observable` object?

You use `@State` (e.g., `@State private var settings = Settings()`) in the *owning* or *source of truth* view to create and manage the lifecycle of your `@Observable` object. You use `@Bindable` when passing this `@Observable` object to a *child view* that needs to create two-way bindings to the properties *of* that object.

What happens if I forget to use `@Bindable` on an `@Observable` object in a child view?

If you pass an `@Observable` object to a child view without `@Bindable` (e.g., `var settings: Settings`), the child view will still observe changes to the object's properties. However, you won't be able to use the `$` prefix (e.g., `$settings.username`) to create two-way bindings for UI controls. You'd only have read-only access to its properties, or you'd have to construct bindings manually, which defeats the purpose of `@Bindable`.

#SwiftUI#Bindable#Observable#Data Flow#Property Wrapper#iOS Development