Passing Mutable @State using @Binding

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In this section, you’ll refactor the Counter app from the previous lesson to better understand and apply SwiftUI’s principles of state management. You’ll learn how to pass a mutable state across different views within the app using @Binding. The term mutable refers to something that can be “mutated” or changed.

Refactoring the Counter Example Into Multiple Views

Your mission is to divide the counter example from Lesson 1 into three separate views:

Creating the ControlPanel View

Open the starter Xcode project located at 02-managing-local-view-state-with-state-and-binding/02-instruction/Starter/Counter.xcodeproj.

Step 1: Create the ControlPanel View

Currently, the Counter app has all the counter UI and logic inside CounterView. To divide the view, you’ll need to create new views for ControlPanel and Console. Start by building ControlPanel.

struct ControlPanel: View {
  // 1
  @State private var count: Int = 0

  // 2
  var body: some View {
    HStack {
      // 3
      Button("Increment") {
        count += 1
      }
      Button("Decrement") {
        count -= 1
      }
    }
  }
}

Step 2: Integrate ControlPanel Into CounterView

Incorporate ControlPanel into CounterView so the counter view can display the control panel buttons.

ControlPanel()

Evaluating the Current Solution

Review the integrated code:

struct CounterView: View {
  @State private var count: Int = 0

  var body: some View {
    VStack {
      Text("Count: \(count)")
      ControlPanel()
    }
  }
}

struct ControlPanel: View {
  @State private var count: Int = 0

  var body: some View {
    HStack {
      Button("Increment") {
        count += 1
      }
      Button("Decrement") {
        count -= 1
      }
    }
  }
}

Implementing a Single Source of Truth With @Binding

It’s a best practice in SwiftUI to manage state in one place and allow changes to propagate throughout the app. Without a single source of truth, values can easily become out of sync, leading to defects and displaying incorrect data. Sound familiar? Maintaining a single source of truth ensures consistency and reduces errors.

Step 1: Update ControlPanel to Use @Binding

Modify ControlPanel to use a binding instead of its own state. This allows the parent CounterView to pass count into ControlPanel so that ControlPanel can change it.

@Binding var count: Int

Step 2: Pass the @State as @Binding From CounterView

Update CounterView to pass @State as a @Binding to ControlPanel so that ControlPanel can change count. Remember, passing the parent’s state allows the app to have a single source of truth for the count.

ControlPanel(count: $count)

Trying It Out

Build and run the app.

Recap: Declaring and Passing a Binding

Here’s a brief overview of the steps to implement a binding in SwiftUI, which you practiced in the Counter app:

Propagating State Changes

Now that you’ve successfully built ControlPanel and connected it using binding, the next step is to refactor the text display that shows the count into a separate Console view. This will help you practice changing state in one subview and reflecting that change in another subview within the same view hierarchy. In effect, the state will be propagated from a single source of truth to multiple other views.

Step 1: Create a New Console View

Build the Console view so the count can be displayed from Console, which will be a subview of CounterView.

// 1
struct Console: View {
  // 2
  let count: Int

  // 3
  var body: some View {
    Text("Count: \(count)")
  }
}

Step 2: Update CounterView to Use Console and Pass the Count to Console

Now, integrate the new Console view into the main view, CounterView. This will allow CounterView to create a new Console view with the current count on initial display and whenever count changes.

Console(count: count)
VStack {
  Console(count: count)
  ControlPanel(count: $count)
}

Understanding Data Flow and State Propagation

Here’s the data flow, step by step:

Passing Bindings to SwiftUI Controls

Now that you understand how to pass a mutable state between custom subviews, it’s time to learn how to pass bindings into SwiftUI’s built-in user controls. This will allow you to create interactive interfaces where controls can modify your app’s state directly. Next, you’ll explore how this works with common SwiftUI controls like toggles and text fields.

Toggles

A Toggle in SwiftUI is a control that allows users to switch between on and off states. It requires a binding to a Boolean value. When the user interacts with the toggle, the bound Boolean value updates automatically, reflecting the current state of the toggle.

@State private var isSwitchedOn: Bool = false

var body: some View {
  Toggle("Enable Feature", isOn: $isSwitchedOn)
}

Text Fields

Similarly, a TextField in SwiftUI uses a binding to a string value, allowing the text field to update its UI as the user types. The text field’s content is directly bound to state, making it easy to capture and respond to user input.

@State private var username: String = ""

var body: some View {
  TextField("Username", text: $username)
}

Bindings Are Two-Way

So far, you’ve seen how a subview can change the state of a parent view. It’s important to note that this interaction isn’t one-sided; a parent can also modify its own state, even if it’s passed down as a binding to a subview. This indicates that bindings in SwiftUI are inherently two-way: Changes in state can originate from either the parent or the subview, and updates are reflected across both. This two-way flow ensures that your UI components remain synchronized. It’s important to keep this in mind as you design view hierarchies for your app.

Wrapping Up

Understanding how to bind state to both custom and provided SwiftUI views is crucial for creating dynamic and responsive apps. You’ve now learned to not only manage state within your custom views but also how to leverage SwiftUI’s powerful data-binding capabilities with built-in controls.

See forum comments
Download course materials from Github
Previous: Introduction Next: Building Financial Entry Form Using @State & @Binding Demo