iExpense Challenges

Day 38 is three challenges on the iExpense app – a simple expense tracking app that uses UseDefaults for storing it’s data.

Locale

Use the user’s preferred currency, rather than always using US dollars.

One of the joys of modern programming (as opposed to mid-1990’s programming) is the ability of the internet to give you answers. I knew the answer to this would be lurking in the locale environment variable, but instead of looking it up, just googled, and found a viable looking solution on Reddit.

Text(amount, format: .currency(code: Locale.current.currencyCode ?? "USD"))

When I paste it into Xcode, another modern miracle occurs – Xcode warns me know currencyCode is out of date:

'currencyCode' was deprecated in iOS 16: renamed to 'currency.identifier'

and offers to fix it for me:

Use 'currency.identifier' instead

So I’m like “sure, fix that for me”. Then there’s an error because the chaining is needs touched up now:

Value of optional type 'Locale.Currency?' must be unwrapped to refer to member 'identifier' of wrapped base type 'Locale.Currency'

and again offers to fix the chaining for me, so okay it, and end up with this:

fileprivate func itemView(item: ExpenseItem) -> some View {
HStack {
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.type)
}
Spacer()
Text(item.amount, format: .currency(code: Locale.current.currency?.identifier ?? "USD"))
}.foregroundColor((item.amount < 10) ? .purple : (item.amount < 100) ? .green : .blue)
}
view raw ItemView.swift hosted with ❤ by GitHub

Conditional formatting

Modify the expense amounts in ContentView to contain some styling depending on their value – expenses under $10 should have one style, expenses under $100 another, and expenses over $100 a third style. What those styles are depend on you.

Easy – ternary operator on HStack – see above

Conditional list building

For a bigger challenge, try splitting the expenses list into two sections: one for personal expenses, and one for business expenses. This is tricky for a few reasons, not least because it means being careful about how items are deleted!

This was tricky, but I didn’t run into deletion problems. I thought I’d just run the ForEach twice and use an if inside it to build two different list sections. The compiler was very upset with this, saying something about not being able to determine the type in the view, but not being able to identify the view.

The solution for this turned out to be to put them inside a Group{}. I guess this means it’s something related to the ways Views are built and the magic inside the @ViewBuilder property wrapper.

List {
Section(header: Text("Personal")) {
ForEach(expenses.items) { item in
Group {
if item.type == "Personal" {
itemView(item: item)
}
}
}
.onDelete(perform: removeItems)
}
Section(header: Text("Business")) {
ForEach(expenses.items) { item in
Group {
if item.type == "Business" {
itemView(item: item)
}
}
}
.onDelete(perform: removeItems)
}
}
.navigationTitle("iExpense")
.toolbar{
Button {
showingAddExpense = true
} label: {
Image(systemName: "plus")
}
}

2 responses to “iExpense Challenges”

  1. iExpense Feedback – dev.endevour Avatar

    […] I finally got around to looking at Paul’s solutions for the iExpense challenges. […]

    Like

  2. Musings on Data – dev.endevour Avatar

    […] iExpense app from the 100 Days course included encoding data to JSON then saving it in UserDefaults, but […]

    Like

Leave a comment