Building Robust ViewModels

Feb 28 2025 · Swift 5.9, iOS 17, Xcode 15.3

Lesson 03: Data Binding Techniques

Demo 2

Episode complete

Play next episode

Next
Transcript

Continue with your project from the first demo or open the TheMet app in the Final demo 1 folder. Now, it’s time to implement your favorite feature in ObjectView.

Open ObjectView. Remember this CountView you added in the previous demo video? You don’t need it anymore, so remove it. Now, just below, there’s a Button with a TODO: Toggle isFavorite. To change a property of object, you need a binding to it, so start by changing its declaration.

@Binding var object: Object

The compiler will complain, but you’ll fix everything soon. While you’re here, add the Button action:

object.isFavorite.toggle()

And the preview needs to pass a binding to object to ObjectView:

return ObjectView(object: $object)

Now, open ContentView to fix the call to ObjectView.

The @Environment object allows only one-way binding — it’s read-only. But you can create a Bindable version of it, just inside the VStack:

@Bindable var twoWayStore = store

Now, use twoWayStore instead of store in List and add bindings to twoWayStore and the object parameter:

List($twoWayStore.objects) { $object in

Finally, pass a binding to object to ObjectView:

ObjectView(object: $object)

Check the app still works and try out the favorite button. The item is still faved when you go back, so everything works when you share the view model in the app’s environment. What if you go back to passing it as a parameter from TheMetApp? Well, that’s easier! In TheMetApp, change ContentView back to receive store as a parameter.

      ContentView(store: store)
//      ContentView()
//        .environment(store)

And back to ContentView to do the same, so ContentView receives store as a parameter, not an environment variable:

  var store: TheMetStore
//  @Environment(TheMetStore.self) var store

Now, store must be a Bindable property:

@Bindable var store: TheMetStore

Scroll down to fix the preview using the same process:

  ContentView(store: TheMetStore())
//  ContentView()
//    .environment(TheMetStore())

Then, back in the VStack, comment out thetwoWayStore property and switch the List statements to use bindings to store and object:

        List($store.objects) { $object in
//        List($twoWayStore.objects) { $object in

Check it all works.

Finally, what if you want to declare the view model in ContentView? Too easy!

Set the scene by editing TheMetApp back to bare bones. Copy the store declaration, then comment it out along with the line that uses it, and uncomment the simple ContentView.

struct TheMetApp: App {
//  @State var store = TheMetStore()

  var body: some Scene {
    WindowGroup {
//      ContentView(store: store)
      ContentView()
//        .environment(store)
    }
  }
}

And back to ContentView to replace Bindable with State:

  @State var store = TheMetStore()
//  @Bindable var store: TheMetStore

And everything just works! You can use a binding to the @State variable store with no issues!

See forum comments
Cinema mode Download course materials from Github
Previous: Instruction Next: Conclusion