Rudy Folden
Rudy Folden

Reputation: 101

SwiftUI TextField Currency Formatting Problem

I'm running into a SwiftUI end-user, usability problem using a TextField to enter a currency amount.

The bound field is a double, initially set to 0 and when the text field is displayed, the prompt is $0.00.

The problem is that when the user wants to enter a value, they have to erase the 0.00 with the backspace key, manually. Also, if they accidentally backspace over the $-sign, any value entered thereafter disappears!

When there are multiple currency fields, this is a real nuisance for the end-user.

I've seen suggestions on the Internet to set the formatter.zeroSymbol = "" in the NumberFormatter, but when numberStyle = .currency any value entered is lost/destroyed.

If I change the number style to .decimal, I can use the zeroSymbol option and it seems to work, but I lose the currency formatting.

Does anyone know how to fix this?

Following is a sample code that you can run that demonstrates this problem.


import SwiftUI

struct ContentView: View {
    @State private var amount: Double = 0
    
    let currencyFormat: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        //                formatter.zeroSymbol = ""
        return formatter
    }()

    var body: some View {
        HStack {
            Text("Enter Amount")
            Spacer()
            TextField("", value: $amount, formatter: currencyFormat)
                .keyboardType(.numbersAndPunctuation)
        }
        .padding()
        
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Upvotes: 4

Views: 3408

Answers (3)

Logan Isitt
Logan Isitt

Reputation: 21

Here's a workaround that worked for me. It uses FocusState to switch between two formatters.

import SwiftUI

struct ContentView: View {
    @State private var amount: Double = 0

    var body: some View {
        HStack {
            Text("Enter Amount")
            Spacer()
            CurrencyTextField(title: "", value: $amount)
        }
        .padding()
    }
}

struct CurrencyTextField: View {

    var title: String
    @Binding var value: Double
    
    @FocusState private var isFocused: Bool

    private let currencyNumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency

        return formatter
    }()

    private let decimalNumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.usesGroupingSeparator = false

        return formatter
    }()

    var body: some View {
        TextField(title, value: $value, formatter: isFocused ? decimalNumberFormatter : currencyNumberFormatter)
            .keyboardType(.decimalPad)
            .focused($isFocused)
    }
}

#Preview {
    ContentView()
}

Upvotes: 2

Marlon Martina
Marlon Martina

Reputation: 51

Elaborating on the answer of @Zulqarnain Naveed:

TextField("",value: $amount, format: .currency(code: Locale.current.currency?.identifier ?? "USD"))

Add the following lines of code. This will select all text in the text field when focussed:

.onReceive(NotificationCenter.default.publisher(for: UITextField.textDidBeginEditingNotification)) { obj in
                    if let textField = obj.object as? UITextField {
                        textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.endOfDocument)
                    }
                }

Upvotes: 5

Zulqarnain Naveed
Zulqarnain Naveed

Reputation: 108

Instead of this one:

TextField("", value: $amount, formatter: currencyFormat)
                .keyboardType(.numbersAndPunctuation)

Use this one:

TextField("",value: $amount, format: .currency(code: Locale.current.currency?.identifier ?? "USD"))

Upvotes: 1

Related Questions