Why does Swift NumberFormatter require a non-breaking space to work properly?
Asked Answered
P

0

7

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?

Pierrepierrepont answered 22/5, 2021 at 18:57 Comment(10)
I cannot tell you why, but adding formatter.isLenient = true makes the conversion work in my test.Cost
I can't find any documentation on why it inserts any kind of whitespace between the currency symbol and the numeric value.Subside
Inserting a non-breaking space in the number-to-string conversion makes some sense, as it prevents the resulting string from being split across lines in multi-line text. Expecting a non-breaking space in the string-to-number conversion makes no sense to me. I have no idea if that is documented and/or to be expected.Cost
@MartinR Interesting, I did the test in currencies which the currency symbol comes before the digits(like Bolivian Bolívar and Brazilian Real) and it worked, but I have no idea why either, isLenient property documentation seems pretty vague about it.Pierrepierrepont
My guess is that without lenient mode, NumberFormatters require that the output of string(from:) and the input to number(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. With isLenient = true, the formatter is willing to overlook the difference between breaking and non-breaking space.Subside
developer.apple.com/forums/thread/124911Ingrained
!= is already a function so you don’t need to make a new closure out of it.Beside
@Jessy posted to wrong question?Ingrained
@Ingrained no he was referring to .filter { $0 != $1 } that can be written as .filter(!=) which I hadn't thought of myself so that was a cool tip. Thanks Jessy.Ryder
@MartinR you should post your isLenient = 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

© 2022 - 2024 — McMap. All rights reserved.