В моем коде есть число, заданное пользователем через TextField.
struct ContentView: View {
@State private var numberText: String = ""
var body: some View {
TextField("0", text: $numberText)
}
}
Мне нравится хранить это число как двойное. Мне известны следующие подходы:
// 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().
Проблема возникает в следующем сценарии. Это мои настройки:
Варианты A, B и C не будут работать, поскольку они не учитывают локаль. Они всегда будут пытаться проанализировать строку с точкой «23,45». Итак, вариант D подходит ближе всего, но если я укажу ему текущую локаль. При этом учитывается регион Нидерландов. Это означает, что он ожидает 23,45
, а не 23.45
. Однако клавиатура использует настройки средства форматирования чисел, поэтому точка в качестве десятичного разделителя будет отображаться правильно. Это означает, что пользователь наберет 23.45
, и парсер выдаст неправильное число.
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)
}
}
У него та же проблема. Если пользователь вводит 23,45
, вместо этого он меняет числовой формат в настройках на использование .
. TextField этого не отражает, но клавиатура (decimalPad) отражает. Таким образом, если пользователь удалил ,45
и теперь ввел .45
, результат двойного значения станет 2345.0
вместо ожидаемого 23.45
.
Похоже, что новые FormatStyle
API не учитывают настройки форматирования чисел устройства и просто используют то, что установлено по умолчанию для локали, установленной в настройке «регион».
С другой стороны, старый NumberFormatter
делает это правильно:
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"))
Вы также можете передать NumberFormatter
в TextField
напрямую:
TextField("Foo", value: $value, formatter: someFormatter)
Вы подтверждаете мои мысли. Что API FormatStyle не учитывает настройки форматирования чисел устройства. NumberFormatter действительно так делает. Это исправило, спасибо за помощь. Также жаль, что TextField не обновляется автоматически до нового числового формата. Таким образом, .
меняется на ,
при изменении форматирования чисел.
Почему бы не работать с двойным значением напрямую, init(_:value:format:prompt:)?