12

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:

  1. I do not know whether UIKit selects the decimal separator exactly this way. The behavior may be different for some languages
  2. It has to be computed every time a number will be formatted, as the user may change keyboards while the app is running.
Palle
  • 11,511
  • 2
  • 40
  • 61
  • In the Settings app under Language & Region, do you have other languages listed under "Preferred Language Order"? Try removing those, if any, and see what happens. – rmaddy Jul 08 '18 at 16:24
  • Changing this setting does not alter the decimal separator. It seems to be the intersection between the set of enabled `UITextInputMode`s and the preferred localizations of the app. Using this approach, I have been successful at determining the decimal pad separator for the languages that I have tested but I have no way of knowing whether my algorithm for that is correct in every situation. – Palle Jul 08 '18 at 17:36
  • 1
    Mate, thanks for this! – juhan_h Oct 10 '18 at 16:27

1 Answers1

2

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.

Erlend
  • 1,711
  • 1
  • 22
  • 25
  • Thanks for your reply. While this approach would work for conversions from Strings to Numbers, it is not applicable to the reverse direction, i.e. pre-filling text fields with default or previously chosen values. (Also, dynamically switching between decimal separators may lead to ambiguities when grouping separators are involved). – Palle Dec 22 '18 at 00:45
  • I’m not sure i understand what you want to do. It seem you want to use the keyboard pad decimal separator for the prefilled values, but it makes more sense for me to use the current lokale for this. And also when converting from numbers to strings. – Erlend Dec 22 '18 at 03:15