Using a Binding in SwiftUI – Simple Example

In my original post on Bindings in SwiftUI, I had mentioned when we might want to use a binding:

You would use a binding to create a two-way connection between a property that stores data, and a view that displays and changes the data.

I showed an example of how we could bind one of our local @State properties to a Toggle. The Toggle could update the state of our property through it.

We can use other system controls, such as TextField or Picker, to also update our local variables. But what if we want to have another one of our views update a variable owned by another? Let’s solve a simple example and, in doing so, we’ll see when we will make use of @Binding.

I have a simple view that displays my name. I am storing it locally as a variable.

struct NameDisplayView: View {
    var name: String = "unknown"
    
    var body: some View {
        Text("Your name is \(name)")
    }
}

It simply takes the text there and outputs "Your name is unknown". It works, but I want to be able to update that name so I can have it show me and not assume I have no name.

Well, we know how we can bind to something from our original post. So let’s make a view where we can change some text.

struct NameChangeView: View {
    @State var text: String
    
    var body: some View {
        TextField("Type Here", text: $text)
    }
}

So far, this is a simple example. If we loaded up NameChangeView, we could tap into the TextField and, as we type, text would be updated. Why? Because we have it bound to the text property on TextField, which is of type Binding<String>. So as the TextField updates the text, our view can get the latest values.

But let’s say we want to present this view as a sheet, let the user update the text, and then send it back to our original view. Can we do it? Yes!

What do we need to change so that our NameChangeView can pass changes back from itself to another view? All we have to do is change @State to @Binding. With that change, we now have two way communication via that variable.

Let’s add a little styling and a way to dismiss the view. That gives us our final form:

struct NameChangeView: View {
    @Environment(\.presentationMode) var presentationMode
    @Binding var text: String
    
    var body: some View {
        TextField("Type Here", text: $text, onCommit: { self.presentationMode.wrappedValue.dismiss() })
        .textFieldStyle(RoundedBorderTextFieldStyle())
        .padding()
    }
}

Now, how can we use this in our original view?

We’ll add a button to trigger a sheet. This happens by binding a local Bool to the .sheet(isPresented:) modifier. Lastly, we add our NameChangeView as what is being presented modally as a sheet. We pass our local name variable as a binding to that view, so it becomes $name.

struct NameDisplayView: View {
    @State var name: String = "unknown"
    @State var showNameChange = false
    
    var body: some View {
        VStack {
            Text("Your name is \(name)")
            Divider()
            Button("Change") {
                self.showNameChange = true
            }
        }
        .sheet(isPresented: $showNameChange) {
            NameChangeView(text: self.$name)
        }
    }
}

Now, we can tap on “Change”, which will present our text field. We can tap on it, change our name, hit “Return”, and be taken back to our original view but with our name in place.

Bindings might seem like a new concept to iOS developers, but they open up a lot of possibilities in Swift. Explore various ways that you can use bindings to communicate between views. You’ll find them very useful, especially in cases of user input as we saw in our example.

You can find the example code in this Github Gist.

Leave a Reply

Your email address will not be published. Required fields are marked *