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.
You’ll learn to build responsive form controls, present forms modally from the main screen, and dynamically handle data to save new entries effectively. This approach ensures that the form not only reacts to user interactions but also maintains the consistency and integrity of the data. Let’s get started!
Open the Starter Xcode project at 02-managing-local-view-state-with-state-and-binding/03-demo/Starter/MyBudget.xcodeproj.
Open the AddFinancialEntryView.swift file, which contains a basic NavigationStack and an empty form.
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.
Specifically, you’ll add the following controls:
-
A
TextFieldfor entering monetary values. -
A
TextFieldfor categorizing these entries. - A toggle switch to designate entries as either expenses or income.
Each control plays a role in capturing the necessary financial entry data.
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
Pass this state property as a binding to the TextField for the amount, allowing the TextField to update the amount state property directly based on user input.
Inside Form, replace // TODO: Implement form. with a new TextField to allow users to enter a monetary amount:
TextField("Amount", value: $amount, format: .number)
.keyboardType(.decimalPad)
This TextField employs a .keyboardType(.decimalPad) view modifier to present a number keypad, simplifying numeric data entry.
Note: This example allows for non-numeric input. Addressing non-numeric input validation is beyond the scope of this lesson.
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 = ""
Pass this state property as a binding into the category TextField.
In the form, below the amount TextField, insert another TextField for category:
TextField("Category", text: $category)
This configuration binds the text field to the category state property, allowing for reactive updates as the user types.
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
Pass this state property as a binding into a Toggle to allow users to specify whether the new entry is an expense or income.
Directly below the category text field, add a Toggle using this state variable to display the current selection:
Toggle(isOn: $isExpense) {
Text("Is Expense")
}
This toggle modifies the isExpense property directly and includes a text label to indicate its purpose to users.
Preview
Run the preview. Confirm that the toggle switches states effectively and updates the user interface as expected.
Having set up the form controls for capturing new financial entries, you’re now ready to build the functionality for presenting the form from the main screen.
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.
To add a sheet in SwiftUI, you start by creating a @State property in the parent view to control when the sheet is shown or hidden. Then, update the child view that you’ll present as a sheet with a @Binding property, allowing it to manage its own visibility based on the state from the parent view.
In the parent view, set up an interactive element like a button that toggles this state, triggering the sheet to open or close. Attach a .sheet view modifier to the parent view, specifying this state variable as the condition for the sheet’s visibility and passing necessary data to the child view.
Follow these steps to add a sheet to the budget-tracking app to present the new financial entry form.
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.
Open AddFinancialEntryView.swift, and add a new showingAddView binding property under the isExpense state property:
@Binding var showingAddView: Bool
The @Binding property allows AddFinancialEntryView to modify the Boolean value that controls its presentation, making it possible to dismiss the view from within. This is necessary because a user interacts with this child view to perform a dismiss.
Step 2: Add Toolbar for Cancel
Next, add a toolbar button to handle cancellation — to dismiss the form without saving the new entry.
Replace // TODO: Implement toolbar. inside the .toolbar view modifier with a cancel toolbar item:
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
showingAddView.toggle()
}
}
The toolbar currently contains one button: Cancel. This button toggles the showingAddView state to false, dismissing the form.
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.
Add this state property inside the preview AddFinancialEntryViewPreview above the view’s body:
@State private var showingAddView = true
Then, pass this into AddFinancialEntryView inside the body:
AddFinancialEntryView(showingAddView: $showingAddView)
Now, run the preview to see the cancel button. Clicking it won’t do anything because the form in the preview isn’t presented modally — you’ll get to try it out in the next step.
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
This state property, showingAddView, is initially set to false, indicating that the form isn’t visible initially.
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
The button’s action changes the showingAddView state to true, which is the trigger for displaying the form.
Attach Sheet to ContentView
Attach the sheet view modifier to List in ContentView after the .navigationBarItems view modifier:
.sheet(isPresented: $showingAddView) {
AddFinancialEntryView(showingAddView: $showingAddView)
}
The .sheet view modifier uses a binding to the showingAddView state and presents AddFinancialEntryView only when this state is true. By passing the $showingAddView binding to AddFinancialEntryView, the form gains the ability to dismiss itself by setting this state to false.
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
to verify it dismisses the modal view, effectively resetting the showingAddView state to false. This interaction ensures a fluid presentation by dynamically controlling the form’s visibility.
With the form now set up to be presented modally, the next step is to integrate the functionality for saving new entries. This process will allow users to not only create and view entries within the form but also save them into the app’s list of entries.
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.
Open AddFinancialEntryView.swift, and add a new financialEntries binding property in AddFinancialEntryView under the showingAddView property:
@Binding var financialEntries: [FinancialEntry]
The @Binding property links AddFinancialEntryView to the financialEntries array in ContentView, enabling direct modifications to this shared data structure. This is required to append new entries to a single source of truth.
Update the preview logic to accommodate this new property. Add the following state property in AddFinancialEntryViewPreview below the showingAddView property:
@State private var financialEntries: [FinancialEntry] = []
Pass this state property as a binding into AddFinancialEntryView in AddFinancialEntryViewPreview’s body:
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()
}
}
The Save button creates a new FinancialEntry instance with a unique ID and the inputs provided in the form. It then appends this new entry to the financialEntries array. Toggling showingAddView dismisses the form, reflecting the UI’s response to the user’s actions.
Step 3: Pass Entries State
Finally, ensure that the ContentView passes the financialEntries array as a binding when presenting AddFinancialEntryView.
Open BudgetTrackerApp.swift, and pass entries into AddFinancialEntryView as a binding in the initializer:
.sheet(isPresented: $showingAddView) {
AddFinancialEntryView(showingAddView: $showingAddView, financialEntries: $entries)
}
This modification in the .sheet modifier passes the entries array to AddFinancialEntryView using a binding. This setup ensures that any changes made in the entry form are directly reflected in the ContentView’s list of entries, maintaining data consistency and enabling UI updates.
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!
Next, you’ll review the key points from this lesson in Lesson 2’s conclusion in preparation for Lesson 3.