I have a UILabel to show input text (which has decimal numbers). What I am trying to do is, that the UILabel text can be shown as per locale. E.g.-
"1234.56" should be shown as "1,234.56" for US locale, "1.234,56" for DE (Germany) locale and so on.
Below is what I have done to achieve this -
NSNumberFormatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
NSNumberFormatter.locale = NSLocale(localeIdentifier: language)
NSNumberFormatter.maximumFractionDigits = 16
let displayVal: Double = NSNumberFormatter.numberFromString(displayText.text!).doubleValue
displayText.text = NSNumberFormatter.stringFromNumber(displayVal)
Where displayText is the UILabel to display input.
It works fine until 4 digits i.e. "1234" was shown as "1,234" (for US locale). But as soon as 5th digit gets added, nil returned for displayVal constant variable.
I could guess from the function definition that there is failure in formatting displayText text string which may be "1,2345" when numberFromString tried to format it as per US locale but failed as this does not comply to US locale format.
Can anyone suggest a way to fix this or nicer way of implementation?
You can do something like the following, namely strip out any thousands separators, get a number from that, and then reformat with the thousands separator:
@IBAction func didEditingChanged(_ sender: UITextField) {
guard let text = sender.text else { return }
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
// get thousands separator
let thousandsSeparator = formatter.groupingSeparator ?? ","
// strip thousands separators out
let stripped = String(text.filter { return String($0) != thousandsSeparator })
// now, re-insert them, if any
if let number = formatter.numberFromString(stripped) {
sender.text = formatter.stringFromNumber(number)
}
}
Assuming you're hooking this up to "Editing Changed" so that it's constantly updated, this introduces a few issues. For example, to enter the trailing decimal place, you might have to manually check for that and allow that to be preserved:
@IBAction func didEditingChanged(_ sender: UITextField) {
guard let text = sender.text else { return }
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
// get thousands separator
let thousandsSeparator = formatter.groupingSeparator ?? ","
// strip thousands separators out
let stripped = String(text.filter { return String($0) != thousandsSeparator })
// see if decimal place found, and if so, set fractional digits accordingly
let decimalSeparator = formatter.decimalSeparator ?? "."
var fractionalDigits: Int?
if let decimalSeparatorIndex = text.range(of: decimalSeparator)?.lowerBound {
fractionalDigits = text[decimalSeparatorIndex...].count(of: "0123456789")
formatter.minimumFractionDigits = fractionalDigits!
}
guard
let number = formatter.number(from: stripped),
var result = formatter.string(from: number)
else {
// do whatever is appropriate if string isn't a number at all
return
}
// re-add trailing decimal place only if decimal separator was found, but no fractional digits were
if fractionalDigits == 0 {
result += decimalSeparator
}
// update text field
sender.text = result
}
Note, that uses this little extension:
extension StringProtocol {
func count(of string: String) -> Int {
return reduce(0) { $0 + (string.contains($1) ? 1 : 0) }
}
}
There are many other refinements you could tackle. For example, if the user is not at the end of the text field when they edit, this will reset the selectedTextRange. You also should probably implement shouldChangeCharactersIn to make sure that you cannot enter value at all where stripped would fail. Etc.
But hopefully this illustrates one approach for capturing text input and updating the result with the formatted decimal number.
For Swift 2 rendition, see previous revision of this answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With