Two-way Bindings in SwiftUI

Bindings and Observables are essential for the basic SwiftUI mechanism. One-way bindings are easier, but what about two-way bindings? You want two values to set observers on each other and when one of them changes, it should also reflect that change in the other value.

In Xcode 13.x and iOS 15.x, Apple introduced a new and cleaner way to provide two-way bindings without much hassle. Let's take a look at them with an example.

We have an app that shows the list of places with related information such as icons and names. The user can edit the nickname with any value they want. We also provide the "View Nicknames" button for all the list items which will show the list of updated nicknames for all the places after the edit operation is complete.

Every time the user edits the place's nickname and taps on the "View Nicknames" button, the app will display an alert with the list of edited nicknames.

Here are the basic building blocks of the app,

  1. We have a Place object with name, image, and nickname
  2. Users can edit nicknames in the list
  3. The app will show the alert dialogue with edited nicknames when the user taps the "View Nicknames" button

import SwiftUI

struct Place: Identifiable {
    let name: String
    let imageName: String
    var nickname: String

    var id: String {
        name
    }
}

struct ContentView: View {

    @State var places = [
        Place(name: "Mumbai", imageName: "car", nickname: "placeholder"),
        Place(name: "Vienna", imageName: "airplane", nickname: "placeholder"),
        Place(name: "Amsterdam", imageName: "camera", nickname: "placeholder")
    ]

    @State private var showingAlert = false

    var body: some View {
        VStack {
            List {
                ForEach($places) { $place in
                    VStack {
                        HStack {
                            Image(systemName: place.imageName)
                            Text(place.name)
                            TextField("Nickname", text: $place.nickname)
                        }
                    }
                }

                let nicknames = places.map { $0.nickname }.joined(separator: "\n")

                Button("View Nicknames") {
                    showingAlert = true
                }.alert(nicknames, isPresented: $showingAlert) {
                    Button("OK", role: .cancel) { }
                }
            }
        }
    }
}
The new syntax allows us to pass binding in the collection that gets attached to the editing TextField. Every time the text field is edited, it gets updated in the original list of places too since they are attached by the two-way binding.
💡
As shown in the above example, we need to pass the variable with binding syntax only when it's a read-write operation (Like an editable text field). In the case of read-only operation (Such as showing it on the static label), we can pass it using the original syntax without $ sign

Let's see the demo now,

0:00
/

And that's it. We successfully achieved the two-way bindings in the collection list!

Summary

This was all about how to achieve the two-way bindings in the collection list using new binding syntax for list-based closures. Two-way bindings is a powerful tool to create apps using an intuitive and readable format. I have used them many times in existing SwiftUI apps and they proved to be much useful to save time for initial development and future maintenance.

As usual, if you have any feedback or comments about this article, please reach out on Twitter at @jayeshkawli.