SwiftUI - Understanding Binding

Martin Lasek
7 min readJun 12, 2019

In this tutorial, we will dive into the fundamentals of @Binding — what it is, why it’s good and how to use it. Oh, we will also cover re-usable views ✨

This tutorial is a natural follow-up of SwiftUI - Dynamic List & Identifable. You can either go for that tutorial first and come back later or be a rebel, skip it and read on 😊

Watch the video tutorial of this article instead: here

Index

1. Create a toggle to show/hide details
2. Understanding Binding
3. Where to go from here

1. Create a toggle to show/hide details

In case you skipped the former tutorial (you rebel) here’s the codebase that we will use to start from in this tutorial: Dynamic List (Codebase).

Let’s add another @State variable of Bool to our View and call it showDetails:

import SwiftUIstruct Pokemon: Identifiable {
...
}
struct ContentView : View {
@State var pokemonList = [ ... ]
@State var showDetails = true
var body: some View {
NavigationView {
List(pokemonList) { pokemon in
HStack {
Text(pokemon.name)
Text(pokemon.type).foregroundColor(pokemon.color)
}
}
.navigationBarTitle(Text("Pokemon"))
.navigationBarItems(
trailing: Button(
action: addPokemon,
label: { Text("Add") }
)
)
}
}
func addPokemon() {
...
}
}

I’m leaving out code parts with dots … simply to save space in this Article 😊

How about we use that Bool to show or hide the type of our Pokemon? What? You knew we were going to do that? Well Watson you’re a good GIF observer..

Alright so let’s add a leading button to our Navigation view and have it render “show” or “hide” depending on whether showDetails is set to true or false:

import SwiftUIstruct Pokemon: Identifiable {
...
}
struct ContentView : View {
@State var pokemonList = [ ... ]
@State var showDetails = true
var body: some View {
NavigationView {
List(pokemonList) { pokemon in
HStack {
Text(pokemon.name)
Text(pokemon.type).foregroundColor(pokemon.color)
}
}
.navigationBarTitle(Text("Pokemon"))
.navigationBarItems(
leading: Button(
action: { self.showDetails.toggle() },
label: { Text(self.showDetails ? "Hide" : "Show") }
),

trailing: Button(
action: addPokemon,
label: { Text("Add") }
)
)
}
}
func addPokemon() {
...
}
}

You can use toggle() on a Bool to switch between its value. We then are using the shorthand if-else notation here to display either “Hide” or “Show”. Your view should now have a new button and should look like this:

Try to run your project and see that the button itself already switches its text!

Remember. When the value of a @State variable changes - the View will re-render.

We can use the same approach we used for our Button text for displaying or hiding the type of our Pokemon:

import SwiftUIstruct Pokemon: Identifiable {
...
}
struct ContentView : View {
@State var pokemonList = [ ... ]
@State var showDetails = true
var body: some View {
NavigationView {
List(pokemonList) { pokemon in
HStack {
Text(pokemon.name)
if self.showDetails {
Text(pokemon.type).foregroundColor(pokemon.color)
}

}
}
.navigationBarTitle(Text("Pokemon"))
.navigationBarItems(
leading: Button(
action: { self.showDetails.toggle() },
label: { Text(self.showDetails ? "Hide" : "Show") }
),
trailing: Button(
action: addPokemon,
label: { Text("Add") }
)
)
}
}
func addPokemon() {
...
}
}

When the View renders and showDetails is true then our Text with the type will be shown. If the View renders and showDetails is false it won’t be shown.

2. Understanding Binding

It’s always a good call to break down a View into smaller components. Let’s do that and refactor the Button that we use to toggle the showDetails variable into its own View. Create a new SwiftUI file and call it ToggleTextButton:

You should end up having this:

We want to put the whole Button code in here like:

import SwiftUIstruct ToggleTextButton: View {
var body: some View {
Button(
action: { self.showDetails.toggle() },
label: { Text(self.showDetails ? "Hide" : "Show") }
)

}
}
#if DEBUG
struct ToggleTextButton_Previews : PreviewProvider {
static var previews: some View {
ToggleTextButton()
}
}
#endif

Now you’ll notice that Xcode complaints a lot and yeah we are using variables here we don’t have like showDetails. Stick with me because this is the beginning of understanding Binding. And it’s luckily pretty simple!

Inside our ToggleTextButton let’s rename and define that missing variable:

import SwiftUIstruct ToggleTextButton: View {
var isOn: Bool
var body: some View {
Button(
action: { self.isOn.toggle() },
label: { Text(self.isOn ? "Hide" : "Show") }

)
}
}
#if DEBUG
struct ToggleTextButton_Previews : PreviewProvider {
static var previews: some View {
ToggleTextButton(isOn: true)
}
}
#endif

As you can see we also adjusted our ToggleTextButton_Previews. And that’s totally fine. This is just a preview where we can put in mock / fake data. It does not affect our production app at all and that fact is actually pretty cool!

Now Xcode still complaints because we’re trying to change a variable inside our struct from within our struct. That’s by default not possible in Swift.

If you have read SwiftUI - Understanding State then this sounds familiar to you. We are going to fix that issue by simply writing @Binding in front of our variable isOn and adjust our ToggleTextButton_Previews to the following:

import SwiftUIstruct ToggleTextButton: View {
@Binding var isOn: Bool
var body: some View {
Button(
action: { self.isOn.toggle() },
label: { Text(self.isOn ? "Hide" : "Show") }
)
}
}
#if DEBUG
struct ToggleTextButton_Previews : PreviewProvider {
@State static var myCoolBool = true // Note: it must be static
static var previews: some View {
ToggleTextButton(isOn: $myCoolBool)
}
}
#endif

Let’s quickly use our Button inside our ContentView before I start explaining what this $ sign does, when to use it and why it’s good. In ContentView:

import SwiftUIstruct Pokemon: Identifiable {
...
}
struct ContentView : View {
@State var pokemonList = [ ... ]
@State var showDetails = true
var body: some View {
NavigationView {
List(pokemonList) { pokemon in
HStack {
Text(pokemon.name)
if self.showDetails {
Text(pokemon.type).foregroundColor(pokemon.color)
}
}
}
.navigationBarTitle(Text("Pokemon"))
.navigationBarItems(
leading: ToggleTextButton(isOn: $showDetails),
trailing: Button(
action: addPokemon,
label: { Text("Add") }
)
)
}
}
func addPokemon() {
...
}
}

If you go and run the app you’ll see that everything works as intended 🥳

Alright. What do we have here. We have a parent View called ContentView that holds a child View called ToggleTextButton. The parent View has a @State variable and depending on the value it renders its View. So far so good. We knew that. We now want our child View (ToggleTextButton) to be able to change the value of the @State variable of its parent View. How can a child View get access to the variable of its parent? If we look inside our child View we see that we’re changing the value of our own variable:

// ToggleTextButton.swiftstruct ToggleTextButton: View {
@Binding var isOn: Bool
var body: some View {
Button(
action: { self.isOn.toggle() },
label: { Text(self.isOn ? "Hide" : "Show") }
)
}
}

In order to create a reference or connection from our child View variable to the parent View variable we have to do 3 things. Write @Binding in front of the child View variable. Have @State in front of the parent View variable and when passing the @State variable into the child View write $ in front of the variable name (without space) as shown. We now established a connection ✨

Whenever we change the variable in our child View it will change the value of its parent View as well! And you remember how @State works right? As soon as the value of a @State variable changes the View gets re-rendered. Think about it. It means that the body of the parent View gets re-rendered. Which means a new instance of our child View is created during that process and a reference of the @State variable is passed into it with the new value.

Your ContentView should look like this now:

Note: @State variables must always have default values. @Bindable must not.

And your ToggleTextButton should look like this:

Worth mentioning: Our PreviewProvider structs have a static variable called previews whereas our View structs have instance variables called body. That also means inside of the previews closure we don’t have access to an instance variable which is the reason we have to declare our @State variable to be static inside our PreviewProvider to be able to mock our Preview. That’s it!

Finally! Here’s our final List that can hide and show the type of Pokemons 🥳

4. Where to go from here

This article is to get you started with Binding — there is way more to come! You can follow me here on Medium as well as on Youtube, Instagram and Twitter to not miss out on all future Articles and Video Tutorials!

I am really happy you read my article! If you have any suggestions or improvements of any kind let me know! I’d love to hear from you! 😊

--

--

Martin Lasek

I'm an always optimistic, open minded and knowledge seeking fullstack developer passionate about UI/UX and changing things for the better :)