Skip to Content
Course content

91: State and Binding Property Wrappers

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've been playing around with SwiftUI, you've probably noticed that the UI doesn't just "change" because you changed a variable. SwiftUI needs to know when to redraw the screen, and that's where @State and @Binding come in. I like to think of these as the "source of truth" and the "remote control."

Let's build a simple User Profile Editor. We want a main screen that displays a username and a separate, smaller component that lets us edit that name. This is the perfect way to see how data flows between views.

Creating the Source of Truth

First, I'll set up the main view. Since the username is something that changes and should trigger a UI update, I can't just use a standard var. I need @State. This tells SwiftUI: "Keep an eye on this value; if it changes, refresh any part of the UI that depends on it."

struct ProfileView: View {
    @State private var username: String = "SwiftLearner_99"

    var body: some View {
        VStack(spacing: 20) {
            Text("Current User: \(username)")
                .font(.title)
            
            // We'll put our editor component here in a moment
            UsernameEditorView(name: username)
        }
        .padding()
    }
}

The "Read-Only" Mistake

Now, I want to move the TextField into its own view called UsernameEditorView to keep things clean. My first instinct is usually to just pass the string along. Let's see what happens when I do that:

struct UsernameEditorView: View {
    var name: String // Just a regular property

    var body: some View {
        TextField("Enter name", text: .constant(name)) 
            .textFieldStyle(.roundedBorder)
    }
}

Here's the problem: this doesn't actually work. The name property in UsernameEditorView is a constant. Even if I could change it, it's just a copy of the string from the parent. The parent ProfileView would never know the child changed anything, and the UI wouldn't update. I've essentially created a "read-only" view.

Creating a Two-Way Connection with Binding

To fix this, I need to stop passing a value and start passing a reference. That's what @Binding is for. It doesn't hold its own data; it just points back to the @State variable in the parent. It's like giving the child view a remote control to the parent's data.

Let's rewrite the editor:

struct UsernameEditorView: View {
    @Binding var name: String // Now it's a binding!

    var body: some View {
        TextField("Enter name", text: $name) 
            .textFieldStyle(.roundedBorder)
    }
}

Notice the $name in the TextField. That dollar sign is critical. In SwiftUI, adding the $ prefix accesses the "projected value," which for a @State or @Binding property, is the binding itself. It allows the TextField to write changes directly back to the source.

Wiring it all together

Now I have to go back to my ProfileView. Since UsernameEditorView now expects a binding, I can't just pass username. I have to pass $username.

struct ProfileView: View {
    @State private var username: String = "SwiftLearner_99"

    var body: some View {
        VStack(spacing: 20) {
            Text("Current User: \(username)")
                .font(.title)
            
            // Pass the binding using the $ prefix
            UsernameEditorView(name: $username)
        }
        .padding()
    }
}

Now, when you type in the TextField, the @Binding updates the @State in the parent, which in turn triggers the Text view to redraw instantly. It's a clean, circular flow of data that keeps your UI in sync without you having to manually call "refresh" methods.




📋 Practical Task

Exercise: Build a Notification Preference Panel

Your task is to create a settings interface that allows a user to toggle their notification preferences.

  • Create a parent view called NotificationSettingsView.
  • Inside the parent, create a @State boolean variable called isNotificationsEnabled.
  • Create a child view called NotificationToggleView.
  • The child view should use @Binding to accept the boolean value.
  • In the child view, implement a Toggle component that is bound to that value.
  • In the parent view, display a Text label that says "Notifications are ON" or "Notifications are OFF" based on the current state, and place the NotificationToggleView below it.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.