SwiftUI — Currency Format Retained as String: Potential for Errors and User Misunderstanding
Ruby Kim
10/10/20243 분 읽기


The Final Product
iExpense: Expense Tracking App
This application helps users track their expenses with features to add or remove entries with varieties of convenience features below.
International Currency Options
Types of budgets
Colour Indicator on user select expense levels — low, medium and high.












image 1, image 2, image 3
The Problem I Faced
The Hole When Using Default Currency Format Modifier
NavigationStack {
Form {
TextField("Name", text: $name)
Picker("Type", selection: $type) {
ForEach(types, id: \.self) {
Text($0)
}
}
HStack {
TextField("Amount", value: $amount, format: .currency(code: "USD"))
.keyboardType(.decimalPad)
}
}
This is working perfectly fine, except one concerning thing. Because of the placeholder in the textfield that is automatically created when format is used, the user must erase all characters and enter the amount. (image1)
This is because by default, what currency format receives is a double number.
If you leave the US$ part like in the image2 and enter a number, that is, the amount, the string type US$ will be mixed, resulting error of 0.00 as the image3.
Building Individual Function To Tackle The Task?
The .format(_:currency:) modifier was introduced in SwiftUI 3, which was released as part of iOS 15 and macOS Monterey (2021).
This formatting API allows you to easily format numbers as currency, percentages, or dates, and it significantly simplifies the process of displaying values in a localized way.
Before that, something like below we used to have for manually setting up and configuring currency formatter with NumberFormatter() from Foundation.
import Foundation
func formatCurrency(_ amount: Double, currencyCode: String) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = currencyCode
return formatter.string(from: NSNumber(value: amount)) ?? "\(amount)"
}
// Usage
let formattedAmount = formatCurrency(1000.99, currencyCode: "USD")
Solution by Building Custom Functions?
: While we could revert to the traditional method and customize the function as needed, should we really do that when we have Apple’s recommended API in place?
Additionally, completely removing the placeholder would negatively impact user experience, like they would be wondered if the form accepts decimal points(cents), how many if so.
My Solution
Use Of Picker And UI/UX Approach
: Create a currency code list and make them selectable with the picker. Set the textField format to .number, and finally pass them to ContentView as a String and Double variable, respectively.
In this case,
The user’s currency is clearly displayed.
Remain the input format in the placeholder.
User can choose from a variety of currencies.
And you still use the currency format to display currency symbols on the expense list in ContentView, by sending the selected String value from AddView.
//AddView
struct AddView: View {
@Environment(\.dismiss) var dismiss
//State variables
@State private var name = ""
@State private var type = "Personal"
@State private var amount = 0.0
@State private var isEditing = false
@State private var currency = "USD"
//variables
var expenses: Expenses
let types = ["Business", "Personal"]
//list any currency codes here to provide
let currencies = ["USD", "GBP", "JPY"]
var body: some View {
NavigationStack {
//Input forms(expense name, type and currency/amount)
Form {
TextField("Name", text: $name)
Picker("Type", selection: $type) {
ForEach(types, id: \.self) {
Text($0)
}
}
HStack {
//use .number format with pre-arranged fraction length
TextField("Amount", value: $amount, format: .number .precision(.fractionLength(2)))
.keyboardType(.decimalPad)
Picker("", selection: $currency) {
ForEach(currencies, id: \.self) { currency in
Text(currency)
}
}
}
}
And sending those inputs along with the selected currency code to display currency symbols together with amount of money in the ContentView like below.
//ContentView
struct ExpenseItem: Identifiable, Codable {
let id = UUID()
let name: String
let type: String
let amount: Double
let currency: String
}
struct ItemList: View {
let item: ExpenseItem
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
}
Spacer()
//utilize currency code getting from currency variable
Text(item.amount, format: .currency(code: item.currency))
}
}
}
In Conclusion
Communication Between Users And Programs
Clearly, there is a contrast between the API modifiers provided by Apple and the features and styles that are difficult to customize.
Therefore, as you study SwiftUI, you realize that approaching problems in a general programming way is not the solution here. But because of that, we can leverage what SwiftUI gives us to come up with a much simpler, more intuitive and more stable solution.
From all of those aspects, I think programming now and in the future will be different in this way. Solve problems by approaching them in a variety of ways, especially those that are less-code or centered on user experience.
In the end, the fundamental reason why we try to create better programs is to provide convenience and accessibility to people.
And yes, I love SwiftUI!