Classes that use @Observable
can be used in more than one SwiftUI view, and all of those views will be updated when the properties of the class change. SwiftUI is really smart here: it will only update those views if they actually used the properties that changed.
In this app, we’re going to design a view specially for adding new expense items. When the user is ready, we’ll add that to our Expenses
class, which will automatically cause the original view to refresh its data so the expense item can be shown.
To make a new SwiftUI view you can either press Cmd+N or go to the File menu and choose New > File. Either way, you should select “SwiftUI View” under the User Interface category, then name the file AddView.swift. Xcode will ask you where to save the file, so make sure you see a folder icon next to “iExpense”, then click Create to have Xcode show you the new view, ready to edit.
As with our other views, our first pass at AddView
will be simple and we’ll add to it. That means we’re going to add text fields for the expense name and amount, plus a picker for the type, all wrapped up in a form and a navigation stack.
This should all be old news to you by now, so let’s get into the code:
struct AddView: View {
@State private var name = ""
@State private var type = "Personal"
@State private var amount = 0.0
let types = ["Business", "Personal"]
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $name)
Picker("Type", selection: $type) {
ForEach(types, id: \.self) {
Text($0)
}
}
TextField("Amount", value: $amount, format: .currency(code: "USD"))
.keyboardType(.decimalPad)
}
.navigationTitle("Add new expense")
}
}
}
Yes, that always uses US dollars for the currency type – you’ll need to make that smarter in the challenges for this project.
We’ll come back to the rest of that code in a moment, but first let’s add some code to ContentView
so we can show AddView
when the + button is tapped.
In order to present AddView
as a new view, we need to make three changes to ContentView
. First, we need some state to track whether or not AddView
is being shown, so add this as a property now:
@State private var showingAddExpense = false
Next, we need to tell SwiftUI to use that Boolean as a condition for showing a sheet – a pop-up window. This is done by attaching the sheet()
modifier somewhere to our view hierarchy. You can use the List
if you want, but the NavigationStack
works just as well. Either way, add this code as a modifier to one of the views in ContentView
:
.sheet(isPresented: $showingAddExpense) {
// show an AddView here
}
The third step is to put something inside the sheet. Often that will just be an instance of the view type you want to show, like this:
.sheet(isPresented: $showingAddExpense) {
AddView()
}
Here, though, we need something more. You see, we already have the expenses
property in our content view, and inside AddView
we’re going to be writing code to add expense items. We don’t want to create a second instance of the Expenses
class in AddView
, but instead want it to share the existing instance from ContentView
.
So, what we’re going to do is add a property to AddView
to store an Expenses
object. Please add this property to AddView
:
var expenses: Expenses
And now we can pass our existing Expenses
object from one view to another – they will both share the same object, and will both monitor it for changes. Modify your sheet()
modifier in ContentView
to this:
.sheet(isPresented: $showingAddExpense) {
AddView(expenses: expenses)
}
We’re not quite done with this step yet, for two reasons: our code won’t compile, and even if it did compile it wouldn’t work because our button doesn’t trigger the sheet.
The compilation failure happens because when we made the new SwiftUI view, Xcode also added some preview code so we can look at the design of the view while we were coding. If you find that down at the bottom of AddView.swift, you’ll see that it tries to create an AddView
instance without providing a value for the expenses
property.
That isn’t allowed any more, but we can just pass in a dummy value instead, like this:
#Preview
AddView(expenses: Expenses())
}
The second problem is that we don’t actually have any code to show the sheet, because right now the + button in ContentView
adds test expenses. Fortunately, the fix is trivial – just replace the existing action with code to toggle our showingAddExpense
Boolean, like this:
Button("Add Expense", systemImage: "plus") {
showingAddExpense = true
}
If you run the app now the whole sheet should be working as intended – you start with ContentView
, tap the + button to bring up an AddView
where you can type in the various fields, then can swipe to dismiss.
SPONSORED AppSweep by Guardsquare helps developers automate the mobile app security testing process with fast, free scans. By using AppSweep’s actionable recommendations, developers can improve the security posture of their apps in accordance with security standards like OWASP.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.