I am trying to find out which decimal separator is used by the decimal pad keyboard in iOS, so I can convert strings entered by the user to numbers with NumberFormatter and back.
As I want to pre-fill the text field with an existing value, I need to have a number formatter that uses the same decimal separator as the decimal pad keyboard.
The language that my device is set to (German, Germany) uses a comma as the decimal separator. I have configured iOS to have the German keyboard as the primary and active keyboard and English (US, QWERTY) as a secondary keyboard.
The app that I am working on only has a base localization, which is English. In the scheme settings, region and language are set to system default.
If I run my app, the decimal separator used by the decimal pad keyboard is ".", which is the decimal separator used by the en-US keyboard, but not the de-DE keyboard. The normal alphabetic keyboard shows the German keyboard layout.
If I remove the en-US keyboard on the iOS device, the decimal separator changes to ",".
How can I reliably find out, which decimal separator is used by the decimal pad keyboard?
None of the solutions that I have tried so far work:
Using the preset decimalSeparator of NumberFormatter always gives ",".
Using Locale.current.decimalSeparator always returns "," as well.
Using textField.textInputMode?.primaryLanguage to figure out the locale always returns de-DE.
Using Bundle.main.preferredLocalizations to figure out the localization used by the app always returns en.
This is how the number formatter is configured:
let numberFormatter = NumberFormatter()
numberFormatter.minimumIntegerDigits = 1
numberFormatter.minimumFractionDigits = 0
numberFormatter.maximumFractionDigits = 2
Edit: It seems to be possible to determine the locale used by the decimal pad by finding matches between the active text input modes and app localizations:
let inputLocales = UITextInputMode.activeInputModes.compactMap {$0.primaryLanguage}.map(Locale.init(identifier:))
let localizations = Bundle.main.preferredLocalizations.map(Locale.init(identifier:))
let locale = inputLocales.flatMap { l in localizations.map {(l, $0)}}
.filter { preferredLanguage, preferredLocalization in
if preferredLocalization.regionCode == nil || preferredLanguage.regionCode == nil {
return preferredLanguage.languageCode == preferredLocalization.languageCode
} else {
return preferredLanguage == preferredLocalization
}
}
.first?.0
?? Locale.current
numberFormatter.locale = locale
However this solution has several disadvantages:
I do not know whether UIKit selects the decimal separator exactly this way. The behavior may be different for some languages
It has to be computed every time a number will be formatted, as the user may change keyboards while the app is running.
My experience is that when you only support English localization, the decimal separator in the decimal keyboard type, will always be .. So you need to force en_US locale in the NumberFormatter when parsing a number from a string.
Here is a code snippet which tries to parse first using en_US, then tries to parse using Locale.current.
func parseNumber(_ text:String) -> Double? {
// since we only support english localization, keyboard always show '.' as decimal separator,
// hence we need to force en_US locale
let fmtUS = NumberFormatter()
fmtUS.locale = Locale(identifier: "en_US")
if let number = fmtUS.number(from: text)?.doubleValue {
print("parsed using \(fmtUS.locale)")
return number
}
let fmtCurrent = NumberFormatter()
fmtCurrent.locale = Locale.current
if let number = fmtCurrent.number(from: text)?.doubleValue {
print("parsed using \(fmtCurrent.locale)")
return number
}
print("can't parse number")
return nil
}
I would appreciate if Apple would add the Locale.current decimal separator as the decimal separator for decimal keyboard types, or else we need to add localization for all locales in order to get this right.
Related
I'm trying to get conversion fro user entered
money strings to NSDecimalNumber
var nsdecimalNumberFromUserInput: NSDecimalNumber? {
let parsed = NSDecimalNumber(string: self, locale: Locale.current)
if parsed == .notANumber {
return nil
}
return parsed
}
UIKeyboardType == .numberPad in UITextField
somehow displays either . or , depending on
the device and ios version (12.4[.1] & 13 beta 8).
All current locales set to Belarus.
What's a reliable way to parse money regardless
of the curved balls .numberPad sends my way?
The decimal separator in the above mentioned locale is , (comma)
Thanks
I use In App Purchases in an iOS app. I want to display the price in the right format depending on the user/device.
Here's my code:
let price=product.price
let numberFormatter = NumberFormatter()
numberFoxrmatter.formatterBehavior = .behavior10_4 //doesn't change anything if I remove this line
numberFormatter.numberStyle = .currency
numberFormatter.locale = product.priceLocale
let formattedPrice=numberFormatter.string(from: price)
But the currency symbol is not the good one and/or misplaced in some cases.
In my example, the price product is $19.99 or 20,99€.
Examples
From device:
product.priceLocale: en_FR#currency=EUR (fixed)
Locale.current: en_FR (current)
Output: €20,99
Should display: 20,99€
From simulator:
product.priceLocale: en_FR#currency=EUR (fixed)
Locale.current: en_US (current)
Output: $20.99
Should display: 20,99€ or $19.99
I have several users who have the same issue with other currencies where the symbol should be placed after the price, unlike the dollars format. And another user who sees $7290 instead of 7290₸ (which is quite a different price...).
I'm pretty sure it has to do with the language setting or the Locale.current. But if I change my primary language to French on my device, I have the same price "€20,99". What is weird is my Locale.current switches to en_US (current).
Any way to solve this?
Another solution I'd be happy with: display the price in dollars for everyone, whatever the user's language & currency.
Try this
let currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
// localize to your grouping and decimal separator
currencyFormatter.locale = Locale.current
// We'll force unwrap with the !, if you've got defined data you may need more error checking
let priceString = currencyFormatter.string(from: 9999.99)!
print(priceString) // Displays $9,999.99 in the US locale
Example
currencyFormatter.locale = Locale(identifier: "fr_FR")
if let priceString = currencyFormatter.string(from: 9999.99) {
print(priceString) // Displays 9 999,99 € in the French locale
}
For more detail please check https://supereasyapps.com/blog/2016/2/8/how-to-use-nsnumberformatter-in-swift-to-make-currency-numbers-easy-to-read
Locale setting is key to the correct output. en_FR reads like English language and french region. This will result in formatted output for an english speaker with french price -> €10.00
Use the simulator and set region & language to french and use Locale.current. It should read fr_FR and give correct output. 10,00€
Did you try to change language and region on the simulator and does it effect priceLocale?
I am trying to get some decimal number from the user inside a UITextfield in iOS Swift. Now the user can input number in his or her local number format as per the locale Settings in iOS. I want to convert this number which is in the user's mother tongue into English number. I searched a lot in this site (stackoverflow.com) and the majority of answers are for conversion from one locale (Chinese, or Arabic or Persian) into English but I want to convert number inputted into any locale format into English. How can I do this? So in nutshell, my question is whether the number being inputted in UITextField is in Hindi, Arabic, Persian, Chinese or whatsoever format as per the locale, I want to convert it into English Number format.
you can use NumberFormatter for that.
check below example:
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
let localNumberInStr = "૨૩"
guard let str = numberFormatter.number(from: localNumberInStr) else {return}
print(str) //"23"
When you check the devices locale you know which locale the user is using.
let locale = Locale.current
Just to improve upon Dharmesh answer, here is the answer wrapped in a helper method for use throughout the code. Obviously, it assumes that while getting user input via UITextField one has considered the number set in the user's locale settings.
func convertLocaleNumberIntoEnglish(localeNumberString: String?) -> String? {
guard let ulocaleNumberString = localeNumberString else {return nil}
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
let localNumberInStr = ulocaleNumberString
guard let number = numberFormatter.number(from: localNumberInStr) else {return nil}
let str = String(format:"%f", number.doubleValue)
return str
}
Situation
I want to format a Double 23.54435678 into a String like 23.54 fps respecting the user's locale.
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
let formatted = formatter.string(from: fps as NSNumber)! + " fps"
For the localized number formatting I use DateFormatter.
Question
How should I handle the unit part? Is it valid to just append the unit to the formatted number? Is the placement of the symbol not locale dependent? How do I handle that?
Cocoa has no built-in support for the unit "frames per second", so you will have to provide the suffix yourself, e.g. using Xcode's localization system.
You still need to format the numeric value with NumberFormatter for the current locale and then insert the resulting number string into the localized format string:
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
let numberString = formatter.string(from: fps)
let formatString = NSLocalizedString("%# fps", comment: "") // provide localizations via .strings files
let fpsString = String(format: formatString, arguments: numberString)
If unit placement is locale-dependent (you will have to this find out yourself for the target locales of your app), you have to deal with this manually as well. You can leverage the localization system here by providing localizations with an adequately positioned placeholder for the numeric value, e.g. %# fps for English and x %# yz for... well, Fantasy Language.
I'm facing a strange scenario in my swift code. I have multiple uitexfields that I have set their keyboard type to decimalPad. My users will be Arabic, so they will either input numbers in English or Arabic. I've no problem with English integers and decimals and Arabic integers. However, if they try to enter arabic decimal numbers, the app crashes. The decimalPad in the iphone shows "," as a decimal separator for Arabic numbers, so I tried to change it to "." because I had to convert Arabic numbers to English ones before doing any mathmatical operation.
In the xCode simulator, it changes "،" to "." and then i can add or subtract or whatever i want with the user's input. BUT i can't do that if i use "," it just
crashes!!!
Here's part of my code where I change the Arabic numbers to English ones:
if income.text?.isEmpty ?? true {
//income.text = "this is empty"
// incomeNumber += 0.0
} else {
if let temp = income.text {
let replaced = String(temp.characters.map {
$0 == "،" ? "." : $0
})
let incomeStr: String = replaced
let incomeFormatter: NumberFormatter = NumberFormatter()
incomeFormatter.locale = NSLocale(localeIdentifier: "EN") as Locale!
let incomeFinal = incomeFormatter.number(from: incomeStr)
benifit.append(Double(incomeFinal!))
}
}
This code works in the simulator, but if I change "،" to "," it doesn't work even though the iphone numberPad has "," NOT "،"!!!