State Management in SwiftUI

Jun 20 2024 · Swift 5.9, iOS 17.2, Xcode 15.2

Lesson 02: Managing Local View State with @State & @Binding

Building Financial Entry Form Using @State & @Binding Demo

Episode complete

Play next episode

Next

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

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

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

Unlock now

Welcome to this video demo on building a financial entry form for a budget-tracking app using @State and @Binding. This session is designed to extend your understanding of form controls, modal presentation, and data flow within SwiftUI.

Part 1: Building the Form Controls

In this first part of the demo, you’ll build the controls for users to enter a new entry in the budget-tracking app. By incorporating state management techniques, you’ll ensure the form is reactive and updates dynamically with user interactions.

Step 1: Implement Amount TextField

First, you’ll implement the amount TextField for entering the monetary value of the transaction. This control allows users to specify the amount involved in each financial entry.

Implementation

Above AddFinancialEntryView’s body, define a new amount state property to store the numeric value entered by the user:

@State private var amount: Double = 0
TextField("Amount", value: $amount, format: .number)
  .keyboardType(.decimalPad)

Preview

Run the preview to check the functionality of the amount field. Notice the reactive behavior of the text field as it updates with each keystroke.

Step 2: Implement Category TextField

Next, you’ll implement the category TextField, which allows users to organize their financial entries by category.

Implementation

Below the amount state property, introduce a new category state property to capture the text input from the user:

@State private var category: String = ""
TextField("Category", text: $category)

Preview

Run the preview and ensure the category text field updates reactively with each keystroke.

Step 3: Implement Expense Toggle

Finally, implement a toggle to allow users to denote if the entry is an expense.

Implementation

Below the category state property, add a state variable to store whether the new entry is an expense:

@State private var isExpense = true
Toggle(isOn: $isExpense) {
  Text("Is Expense")
}

Preview

Run the preview. Confirm that the toggle switches states effectively and updates the user interface as expected.

Part 2: Presenting the Form

In this part of the demo, you’ll present the financial entry form view using a SwiftUI sheet.

Sheet Presentation in SwiftUI

In SwiftUI, sheets are used to present new content modally over existing content. To manage the presentation of a sheet, you need a binding to a Boolean state variable. This state controls whether the sheet is presented or not. By toggling this Boolean value, you can show or hide the sheet dynamically.

Step 1: Add a @Binding Property

First, modify AddFinancialEntryView to include a @Binding property. This property will allow the form to control its presentation state directly.

@Binding var showingAddView: Bool

Step 2: Add Toolbar for Cancel

Next, add a toolbar button to handle cancellation — to dismiss the form without saving the new entry.

ToolbarItem(placement: .cancellationAction) {
  Button("Cancel") {
    showingAddView.toggle()
  }
}

Preview

Before running the preview to see the cancel button, you need to make some changes to your preview code to accommodate the new @Binding property and sheet presentation logic.

@State private var showingAddView = true
AddFinancialEntryView(showingAddView: $showingAddView)

Step 3: Set Up ContentView

First, open BudgetTrackerApp.swift to configure the main user interface for presenting the form. This step involves preparing ContentView to manage the visibility of the form with a state variable.

Implement State to Track Visibility

In ContentView under the entries state property, add a new state property to track whether the form should be shown. This Boolean state acts as a control for the modal presentation of the sheet:

@State private var showingAddView = false

Modify the Navigation Bar Button

Modify the plus navigation bar item to present the sheet when pressed. Replace the bar button’s action with:

self.showingAddView = true

Attach Sheet to ContentView

Attach the sheet view modifier to List in ContentView after the .navigationBarItems view modifier:

.sheet(isPresented: $showingAddView) {
    AddFinancialEntryView(showingAddView: $showingAddView)
}

Preview

Run the preview to see the implementation in action. Click the add button to present the form and observe it overlaying the current content. Test the Cancel button within the form

Part 3: Saving New Entries

The next step is enabling the saving of new entries. This involves passing data between views using bindings.

Using @Binding to Enable Saving New Entries

First, you’ll add a @Binding property to AddFinancialEntryView to directly access and modify the main list of financial entries. Next, you’ll implement a save button within the form’s toolbar, which will save new entries to this array and dismiss the form. Finally, you’ll pass the ContentView’s entries state property to AddFinancialEntryView as a binding to keep all data perfectly in sync.

Step 1: Add @Binding for Financial Entries

First, modify AddFinancialEntryView to include a @Binding property for the array of financial entries. This lets the form modify the array directly.

@Binding var financialEntries: [FinancialEntry]
@State private var financialEntries: [FinancialEntry] = []
AddFinancialEntryView(showingAddView: $showingAddView, financialEntries: $financialEntries)

Step 2: Add Save Toolbar Item

Next, add a toolbar item for the save button under the cancel toolbar item inside the .toolbar view modifier.

ToolbarItem(placement: .confirmationAction) {
  Button("Save") {
    let newEntry = FinancialEntry(id: UUID(), amount: amount, category: category, isExpense: isExpense)
    financialEntries.append(newEntry)
    showingAddView.toggle()
  }
}

Step 3: Pass Entries State

Finally, ensure that the ContentView passes the financialEntries array as a binding when presenting AddFinancialEntryView.

.sheet(isPresented: $showingAddView) {
  AddFinancialEntryView(showingAddView: $showingAddView, financialEntries: $entries)
}

Preview

Run the preview to try out adding a new entry and seeing it show up in the list.

Conclusion

You’ve now successfully built the budget-tracking app’s entry form!

See forum comments
Cinema mode Download course materials from Github
Previous: Passing Mutable @State using @Binding Next: Conclusion