I have a UITextField with a mask formatting my input to the BRL currency, so input 1700 from keyboard results in R$ 1.700,00 text, but since I need this value as double, NumberFormatter was used like this:
var currencyFormatter: NumberFormatter {
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "pt_BR")
formatter.numberStyle = .currency
formatter.maximumFractionDigits = 2
return formatter
}
But when I tried to convert to NSNumber it gives me nil.
let input = "R$ 1.700,00"
currencyFormatter.number(from: input) // prints nil
Oddly, the converter works when a number is provided, and the string result can be converted to a Number.
let string = currencyFormatter.string(from: NSNumber(1700.00)) // prints "R$ 1.700,00"
currencyFormatter.number(from: string) // prints 1700.00
I started to investigate and find out what the difference is between my input and the resulting currencyFormatter string from the code below.
let difference = zip(input, string).filter{ $0 != $1 } // Prints [(" ", " ")]
It may look the same but currencyFormatter generates a string with a non-breaking space after the currency symbol while my input has a normal space. Replacing my input space with a non-breaking space makes the formatter work properly.
let nonBreakingSpace = "\u{00a0}"
let whitespace = " "
var convertedInput = "R$ 1.700,00".replacingOccurrences(of: whitespace, with: nonBreakingSpace) // prints R$ 1.700,00 or R$\u{00a0}1.700,00
currencyFormatter.number(from: convertedInput) // Now it works and prints 1700.00
Finally my question is, why does NumberFormatter with a currency numberStyle only work with a non-breaking space in this scenario?
formatter.isLenient = true
makes the conversion work in my test. – Coststring(from:)
and the input tonumber(from:)
have to match exactly. As Martin says, using a non-breaking space makes sense so the whole currency string doesn't get split between lines. WithisLenient = true
, the formatter is willing to overlook the difference between breaking and non-breaking space. – Subside!=
is already a function so you don’t need to make a new closure out of it. – Beside.filter { $0 != $1 }
that can be written as.filter(!=)
which I hadn't thought of myself so that was a cool tip. Thanks Jessy. – RyderisLenient = true
trick as an answer, along with the link Matt posted, where Quinn (The Eskimo) talks about number formatters being focused on display, and subject to change over time. – Subside