Why @Bindable and not @Binding in SwiftUI for iOS 17

As Natascha notes in her helpful article

https://tanaschita.com/20230807-migrating-to-observation/

pre-iOS17 was like this,

           Stateview      Subview
-----------------------------------------
Value Type @State         @Binding
Ref Type   @StateObject   @ObservedObject

With iOS 17, it's like this:

           Stateview      Subview
-----------------------------------------
Value Type @State         @Binding
Ref Type   @State         @Bindable

I like how they simplified @State and @StateObject into just @State for both cases. I'm curious, though, why didn't they simplify @Binding and @ObservedObject into just @Binding? Why did they need to maintain the separate property wrapper @Bindable? I'm sure there's a good reason, just wondering if anybody knew why.

Interestingly, you can use both @Binding and @Bindable, and they both seem to work. I know that you're supposed to use @Bindable here, but curious why @Binding works also.

    import SwiftUI
    
    @Observable
    class TestClass {
        var myNum: Int = 0
    }
    
    struct ContentView: View {
        @State var testClass1 = TestClass()
        @State var testClass2 = TestClass()
    
        var body: some View {
            VStack {
                Text("testClass1: \(testClass1.myNum)")
                Text("testClass2: \(testClass2.myNum)")
// Note the passing of testClass2 without $. Xcode complains otherwise.
                ButtonView(testClass1: $testClass1, testClass2: testClass2)
            }
            .padding()
        }
    }
    
    struct ButtonView: View {
        @Binding var testClass1:TestClass
        @Bindable var testClass2:TestClass
    
        var body: some View {
            Button(action: {
                testClass1.myNum += 1
                testClass2.myNum += 2
            } , label: {
                Text("Increment me")
            })
        }
    }
Answered by ForumsContributor in

I think you're getting confused with all the property wrappers now that iOS 17 and the Observation framework have been introduced. I can give you a quick run down of what each of them do now.

State

  • Use to create a single source of truth for a given value type in a view.

  • Shouldn't be used for reference types as SwiftUI won't update the view when one of its properties changes.

    @State private var isFavorite = false // Bool is a value type
    
    Toggle("Favorite", isOn: $isFavorite) // access the Binding value through $ prefix
    


Binding

  • Use to create a two-way connection between a property that stores data and a view that displays and changes the data.

  • It connects a property to a source of truth stored elsewhere instead of storing data directly.

    @Binding var isFavorite: Bool
    
    Toggle("Favorite", isOn: $isFavorite) // access the underlying Binding value through $ prefix
    

    Would be passed in from parent like so:

    @State private var isFavorite = false
    
    ToggleView(isFavorite: $isFavorite) // expects a Binding so give it one using the $ prefix
    


Environment

  • Use to read a value stored in a view's environment:

    • An EnvironmentValues environment value
    @Environment(\.colorScheme) private var colorScheme: ColorScheme
    
    • A type conforming to the Observable protocol.
    @Observable final class Profile {
        var name: String
    
        init(name: String) {
            self.name = name
        }
    }
    
    @Environment(Profile.self) private var profile: Profile
    


Bindable

  • Use to create bindings to mutable properties of a data model object that conforms to the Observable protocol.

    // Can be applied to an initialised new instance
    @Bindable private var profile = Profile(name: "John Doe")
    
    // Can be applied to a parameter property
    // This acts as a standard property and the value is passed as an argument as normal
    @Bindable var profile: Profile
    
    
    TextField("Name", text: $profile.name) // Binding object can be extracted with the help of Bindable
    
  • Can also be used in conjunction with Environment:

    @Environment(Profile.self) private var profile: Profile
    
    @Bindable var profile = profile // must be placed in view body
    TextField("Name", text: $profile.name) // Binding object can be extracted with the help of Bindable
    


No Property Wrapper

  • Use for constant properties or similar

    let viewWidth = 300.0
    
  • Use for storing an instance of a type conforming to the Observable protocol.

    // Can be as an initialisation of a new instance
    let profile = Profile(name: "John Doe")
    
    // Can be as a parameter property
    let profile: Profile
    

SwiftUI will automatically update the view body when any of the observed values changes. No need for a property wrapper here: it's all done behind the scenes.



So, to answer your original question, Binding and Bindable are two completely different property wrappers used for different purposes. Binding is used for reading and writing a value owned by a source of truth, whereas Bindable is used only to create bindings from the properties of observable objects.

You can check out the documentation for more information:

and also watch the WWDC23 session video Discover Observation in SwiftUI.

Hi, thanks so much for responding. For @State, you said,

  • Use to create a single source of truth for a given value type in a view.
  • Shouldn't be used for reference types as SwiftUI won't update the view when one of its properties changes.

However, I don't think this is right (it was pre-iOS17, but not now). In the video at 4:50, he shows Donut being used with @State:

and at 6:19 he shows that Donut is a class and not a struct, so it is a reference type:

This makes sense to me, because from everything I've read, they've consolidated the pre-iOS17 @State (for value types) and @StateObject (for reference types) into @State (for both value types AND reference types). As Natascha says in her article,

Before the Observation framework, we used @State for value types and @StateObject for reference types to let SwiftUI update views whenever changes occured. With Observation, both types are covered by @State:

In the book "Working With Data" by Mark Moeykens of Big Mountain Studio, he has a great illustration of the pre-iOS 17 usage:

and based on Natascha's article, the new usage will be like this:

I'm curious why

  1. They merged @State and @StateObject into just @State for both value and ref types, but didn't do the same for @Binding and @ObservedObject into just @Binding, but rather kept @Binding as-is, and just changed the name of @ObservedObject into @Bindable (as Natascha says, "Before the Observation framework, we used @Binding for value types and @ObservedObject for reference types to create two-way bindings. With Observation, we use @Binding for value types and @Bindable for reference types").
  2. @Binding actually seems to work for a ref type.
Accepted Answer

Ok. This explanation helps more with where your thinking's at.

First of all, after doing some testing, you are correct in saying @State and @Binding now work with reference types. This seems a little strange to me as in the earlier betas I tried this and views weren't being updated (what I thought should happen). So maybe something has changed since then, I'm not sure. It does however make @Bindable sort of in a strange place as bindings can just be extracted from State or Binding.

Hopefully, the documentation can be updated to show what property wrapper needs to be used for what, because at the moment it seems like they all do similar things now.


‎Secondly:

and just changed the name of @ObservedObject into @Bindable

This isn't technically true. @ObservedObject was needed regardless in order for SwiftUI to watch the model object's properties for changes. Now SwiftUI can watch for changes without a property wrapper, and more efficiently too: only when properties that are accessed change will the view be reloaded.

@Bindable is more of an attachment to the model allowing binding creation. I don't think it does anything regarding observation itself.

Interesting about the change from the early betas! This is my first testing of it so I don't have any earlier experience. I did a quick test and you're absolutely right with @ObservedObject--if I pass a @StateObject into a subview and I don't prefix the var with @ObservedObject, then even if I'm just observing its properties, and even though the properties have @Published, the change doesn't appear in the subview. That's different from @State and @Binding, where you don't need @Binding if you're just observing it, just if you're going to change it. I just tried it with iOS 17 and confirmed that: If I have pass down an object, I don't need @Bindable just to observe changes--those do publish. Strangely, I'm able to update a property on the class in the subview and the change does propagate to the parent where @State resides, though, almost like @Bindable isn't needed even for making updates. Maybe it's one of those cases where it could work, but isn't guaranteed to work so you shouldn't rely on it. Thanks a lot for engaging me in this discussion and helping me understand it better, I appreciate it.

I didn't find a difference between defining an @Observable class object with and without @State property wrapper. In the video, in order to fully adopt the new Observation framework, they emphasize to use only 3 property wrappers (although we can still use other wrappers as they are not deprecated at least today): @State, @Bindable and @Environment, so we can live (somehow) without @Binding, and YES, @State can be used with value and reference types although the docs say: Use state as the single source of truth for a given value type that you store in a view hierarchy, which's misleading.

Now if I want to live without @Binding and to create bindings to my struct data model's properties, I have to convert my struct data model to @Observable class, I don't know if this is the best available approach to fully adopt Observation

Why @Bindable and not @Binding in SwiftUI for iOS 17
 
 
Q