100 Days of SwiftUI
2026-05-25
Project 7, part 1
Your core SwiftUI skills are strong, so it’s time to push past the basics and start building bigger apps.

iExpense: Introduction
Learning how to
- Present and dismiss a second screen of data.
- Delete rows from a list
- Save and load user data
- …
Using @State with classes
@Stateis for the current view- recall that structs are independent, so changing one does not change other copies of it
- classes all point to the same data, so changing it one place changes it everywhere
- If you define a class, say
User()and change it in a view, the@Statevariable won’t track changes to it- to “fix” this, use the
@Observablekeyword before the class definition
- to “fix” this, use the
Showing and hiding views
- sheet is a new view presented on top of the existing one
- similar to alerts, we define the conditions when it should be shown
- you can pass variable values to the sheet if needed
- dismissing the sheet view
- drag it out of the way
- add something like
@Environment(\.dismiss) var dismissto the second view and put adismiss()button on it
@Environmentallows us to create properties that store values provided to us externally (from the user’s device settings)
To dismiss another view we need another property wrapper – and yes, I realize that so often the solution to a problem in SwiftUI is to use another property wrapper!
Deleting items using onDelete()
This is another place where SwiftUI does a heck of a lot of work on our behalf, but it does have a few interesting quirks…
onDelete()is almost exclusively used withForEachandListonDelete()modifier only exists onForEachbecauseListcan have static rows (Quirk 1)- think of
IndexSetas a helper foronDelete()- it’s a Set of Indexes that happen to be sorted, and you make a
funcfor.onDelete(perform: func)and put it on theForEach
- it’s a Set of Indexes that happen to be sorted, and you make a
- user can then delete row by swiping left
- and we get a “free” toolbar button for
Edit/Deleteto do several at the same time (although for this implementation it’s faster and easier to swipe).toolbar { EditButton() }
Storing user settings with UserDefaults
- users expect their preferences to stick
UserDefaultsare good for a few preferences (how much is too much? It depends on how long it takes to load when the app starts)- when was the app last launched
- last item read
- app preferences (e.g. music: on or off?)
- write to
UserDefaultswhen something happens you want to track(value, forKey:"key")e.g.UserDefaults.standard.set(tapCount, forKey: "Tap") - call the stored data when you initialize the struct:
@State private var tapCount = UserDefaults.standard.integer(forKey: "Tap") - supported by the property wrapper
@AppStorage, which is useful for simple data (not structs, for instance) - reduces the code to
@AppStorage("tapCount") private var tapCount = 0and no call toUserDefaultsis required
Archiving Swift objects with Codable
- for when
@AppStorageisn’t enough Codableprotocol is frequently used withJSONEncoderand its counterpartJSONDecoder- for example, this saves data to
UserDefaultsdatais a constant of typeData- anything that is data can be stored as type
Data
Button("Save User") {
let encoder = JSONEncoder()
if let data = try? encoder.encode(user) {
UserDefaults.standard.set(data, forKey: "UserData")
}
}