In my code I have a number that has been set by a user through a TextField.
struct ContentView: View {
@State private var numberText: String = ""
var body: some View {
TextField("0", text: $numberText)
}
}
I like to store this number as a double. I know of the following approaches:
// Option A
Double(numberText)
// Option B
try? Double(self, format: .number)
// Option C
(self as? NSString)?.doubleValue
// Option D
let formatStyle = FloatingPointFormatStyle<Double>(locale: locale)
let parseStrategy = FloatingPointParseStrategy(format: formatStyle, lenient: true)
let formatInput = try? parseStrategy.parse(self)
// Option E
Manually parse by removing the locale.groupingSeparator and by replacing the locale.decimalSeparator with a '.' and then parse it with Double().
The problem occurs in the following scenario. These are my settings:
Option A, B and C won't work, because they won't take a locale into consideration. They will always try and parse a string with a dot "23.45". So option D comes the closest, but if I give it the current locale. It take the Region, Netherlands into consideration. Meaning it expects 23,45
and not 23.45
. However the keyboard uses the setting of the Number Formatter so it will properly show a dot as decimal separator. Meaning the user will type 23.45
and the parser will produce the wrong number.
extension String {
var doubleValue: Double? {
var string = self.trimmingCharacters(in: .whitespacesAndNewlines)
if let groupingSeparator = Locale.current.groupingSeparator {
string = string.replacingOccurrences(of: groupingSeparator, with: "")
}
if let decimalSeparator = Locale.current.decimalSeparator, decimalSeparator != "." {
string = string.replacingOccurrences(of: decimalSeparator, with: ".")
}
return Double(string)
}
}
It seems like the new FormatStyle
APIs don't honour the device's number formatting settings, and just uses whatever is default for the locale set in the "region" setting.
On the other hand, the old NumberFormatter
does do this correctly:
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
// locale is Locale.current by default. You can optionally set this to the local from @Environment(\.locale)
// formatter.locale = ...
print(formatter.number(from: "1,6"))
You can also pass a NumberFormatter
to a TextField
directly:
TextField("Foo", value: $value, formatter: someFormatter)