Search code examples
iosswiftcore-dataswiftuicurrency

Rounding currency values (SwiftUI, Core Data)


I am trying to make a simple TextField in SwiftUI that will format provided values as valid value for currency. I need to round the value to 2 digits. And store the rounded value using Core Data (this I know how to do, I am using Decimal type, BTW).

So far I have this:

import SwiftUI

struct ContentView: View {
    @State private var newValueAsString = ""
    @State private var value: NSDecimalNumber = 0
    
    let decimalBehavior = NSDecimalNumberHandler(roundingMode: .plain, scale: 2, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: true)
    
    var body: some View {
        VStack {
            TextField("0", text: $newValueAsString, onCommit: {
                self.value = NSDecimalNumber(string: self.newValueAsString).rounding(accordingToBehavior: self.decimalBehavior)
                print("\(self.value)")
            }
            )
                .multilineTextAlignment(.trailing)
                .font(Font.system(size: 30))
                .keyboardType(.decimalPad)
            
            // Just to test. Later I will save the value to Core Data.
            Text("\(self.value)")
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

THE PROBLEM

It doesn't work properly when iOS region uses commas instead of dots (for example, Poland). Decimal keyboard has "," not "." and when I use "," numbers are not rounded properly. 123,456 becomes 123. And with regions using dots (for example, USA) 123.456 becomes 123.46 as expected.

What am I missing here? My guess is NumberFormatter, but I cannot manage to make it work.


Solution

  • NSDecimalNumber can be initialized with a string & locale. The documentation explicitly mentions that locale is used for interpreting the decimal separator (emphasis mine):

    locale : A dictionary (!) that defines the locale (specifically the decimalSeparator) to use to interpret the number in numericString.

    So you could do something like:

    // Here I am passing the current Locale but you should pass whatever makes sense to your input
    self.value = NSDecimalNumber(string: self.newValueAsString, locale: Locale.current).rounding(accordingToBehavior: self.decimalBehavior)
    

    Example:

    print(NSDecimalNumber(string: "100.50", locale: Locale(identifier: "en_US"))) // => 100.5
    print(NSDecimalNumber(string: "100,50", locale: Locale(identifier: "el_GR"))) // => 100.5