How to format UILabel text to locale specific NSNumberFormatterStyle.DecimalStyle - ios

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.

You need to make sure the number formatter matches the strings that you pass to it. If there is any mismatch, it is going to return nil, and the doubleValue call is going to return 0.
If you have strings formatted for the US, with a grouping separator of a comma, then you should set the groupingSeparator property of the number formatter to a comma. Then the formatter should handle input strings like "1,234". As you say, "1,2345" is not a normal US format for displaying a number.
You might need to change the groupingSize setting as well (The string "1,2345" would have a groupingSize of 4, if I'm not mistaken.
Where are the strings you are parsing coming from? Do you know what locale they use? Is the user entering "1,2345" on a device that is set up using the en-US locale"? If so, why? That's not normal formatting for US English.

Related

Converting a value to double always returns sceintific format in Swift

I am getting some coordinates from server in string array. And I am trying to save those coordinates in SQLite Database by splitting and converting them to double value. But some coordinates are getting saved in scientific notations. For example I am getting the following coordinate from server:
"-0.0000558,51.3368066"
I am splitting the string and converting it to double resulting in the following values:
[-5.58e-05,51.3368066]
I have tried following solutions but still returning same result:
1.
Double(latLongArr[0])
extension String{
var doubleValue: Double? {
return NumberFormatter().number(from: self)?.doubleValue
}
}
extension String{
var doubleValue: Double? {
let numberFormatter = NumberFormatter()
numberFormatter.allowsFloats = true
numberFormatter.maximumFractionDigits = 10
numberFormatter.numberStyle = .decimal
return numberFormatter.number(from: "\(self)")!.doubleValue
}
}
I have used the above code but it still returns in scientific format but I need it in normal decimal format. So what is the issue?
The last option is the option I would go for and I believe it works right.
I believe your issue is only when you print to console:
As you can see, the double variable is actually converted properly but just when it is formatted to print to the console it shows it as a scientific notation string.
Your other option besides using doubleValue is to use decimalValue
I suggest putting a breakpoint and checking the actual value of your double than reviewing it from the console output which is a formatted string.
Just for reference, code used in the image above:
let number = "-0.0000558"
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 10
let finalNumber = numberFormatter.number(from: number)
let decimalNumber = finalNumber!.decimalValue
let doubleNumber = finalNumber!.doubleValue
print(decimalNumber)
print(doubleNumber)
If you want to print your Doubles without scientific notation use
String(format: "%.7f", value).
Example:
let value = Double(3.141592)
print(String(format: "%.7", value)
will print 3.1415920.
I have used the below extension to represent scientific values in the decimal format.
extension String {
func getDecimalValue() -> Decimal{
return NSNumber(value: Double(self)!).decimalValue
}
}
Usage:
let numberString = "+5.58e-05"
print(numberString.getDecimalValue()) //0.0000558

Can't get correct number formatting as a string [duplicate]

This question already has answers here:
Is floating point math broken?
(31 answers)
Closed 2 years ago.
I've developed a custom control for money input, which contains UITextField and UILabel. When the user taps on it, it becomes active and switches to the UITextField for data input and accepts only numbers and dot symbol, when the user finishes editing it becomes passive and switches to UILabel just to show formatted money value. But there is one little issue which I'm unable to fix a lot of days already.
Let's say the user writes down 88.99 and presses done, this becomes "$ 88.99" in a UILabel, next when the user again taps on it to edit the initial value I get the following value "88.98999999999999". To not present the entire code I selected the core part in a playground format which gives the same result as in my complete project:
extension NumberFormatter {
static public func defaultCurrencyFormatter() -> NumberFormatter {
let formatter = NumberFormatter()
formatter.usesGroupingSeparator = true
formatter.numberStyle = .currency
formatter.currencySymbol = ""
formatter.minimumFractionDigits = 1
formatter.maximumFractionDigits = 2
formatter.currencyGroupingSeparator = ","
formatter.currencyDecimalSeparator = "."
return formatter
}
}
let stringValue = NumberFormatter.defaultCurrencyFormatter().number(from: "88.99")?.stringValue
print(stringValue) // result is Optional("88.98999999999999")
I have no idea why using this NumberFormatter I get such a result. I was thinking that explicitly setting minimumFractionDigits and maximumFractionDigits will solve my issue but it does not affect my result
NumberFormatter is legacy from objc and it operates with NSNumber/CGFloat etc. and usually it is helpful for localized text formatting. Much powerful and convenient parser for numbers is Scanner but if you don't have complex data structure to parse and don't want to deal with Floating-point error mitigation just use swift's Float:
// Float from string
if let float = Float("88.99") {
print(float)
// String from float
let text = String(float)
print(text)
}
Prints:
88.99
88.99
Try this:
extension String {
var currencyStyle: String? {
let formatter = NumberFormatter()
formatter.minimumFractionDigits = 1
formatter.maximumFractionDigits = 2
formatter.usesGroupingSeparator = true
formatter.groupingSize = 3
formatter.currencyGroupingSeparator = ","
formatter.currencyDecimalSeparator = "."
if let double = Double(self) {
let number = NSNumber(value: double)
return formatter.string(from: number)
}
return nil
}
}
to use it:
let str = "12388.98999999999999".currencyStyle
print(str) // Optional("12,388.99")

trying to convert a string to a decimal for iOS 13

I have an app that this was all working correctly before iOS 13. I've checked a few posts but they seem to be saying what I have already done.
I'm passing a string that has currency symbols and formatting, and I want to strip that and use the string value.
func changeCurrencyToDecimal(stringNumber:String) -> Decimal {
let numberFormatter = NumberFormatter()
// Pull apart the components of the user's locale
var locComps = Locale.components(fromIdentifier: Locale.current.identifier)
// Set the specific currency code
locComps[NSLocale.Key.currencyCode.rawValue] = options?.currencyCode // or any other specific currency code
// Get the updated locale identifier
let locId = Locale.identifier(fromComponents: locComps)
// Get the new custom locale
//numberFormatter.locale = Locale(identifier: locId)
if(options?.currencyCode == nil) {
print("There is no currency code so use the default locale")
numberFormatter.locale = Locale.current
}
else{
print("We have a currency code!")
numberFormatter.locale = Locale(identifier: locId)
}
numberFormatter.numberStyle = .currency
numberFormatter.currencySymbol = ""
numberFormatter.decimalSeparator = ","
print("\(stringNumber) is the number being passed")
let number = numberFormatter.number(from: stringNumber)
// check to see if the number is nil and if so, return 0.
print("\(number) is the number converted")
if number == nil{
return 0
}
else{
print("\(number!) is the number")
let amount = number?.decimalValue
return amount!
}
}
An example of a string that I am passing: $300.00 the $ always triggers a return of nil. If I pass 300.00, then the converter works just fine. But I need to be able to check for any currency the user has set for their device.
The currency codes that I am checking are the ones Apple supplies in the var currencyCode: String? { get } options is just where I am storing those codes.
The numberFormatter produces nil every time, and it seems because my decimal is not being stripped of its formatting.
Again this worked before iOS 13, so I am guessing something changed on Apple's side and I just might not have come acrossed it yet.
UPDATE
Here is a user seniaro. The user enters an amount. If the user hits save right away, I take the amount and convert it into a decimal to save in coredata. But if the user dismisses the keyboard, I take that amount and convert it into a string to display the currency symbol. I allow the user to set their currency, so using the locale doesn't work for me, as sometimes they are not using that system. So after the keyboard is dismissed and their amount is displayed with the correct formatting, I have it programmed that if they would happen to change their amount and once again dismiss the keyboard, and the same steps are repeated. The currency symbols are stripped and the string is converted into a decimal.
I hope this better gives you an idea of how this works in my app.
UPDATE
I've added an if/else statement to see if the locale has been set or if it comes back nil, and if so to set it to the Locale.current
Assuming the input string is using the device's current locale you can create a NumberFormatter with Locale.current and set the numberStyle to currency.
I used a small playground to test it:
import Foundation
func changeCurrencyToDecimal(_ stringNumber: String) -> Decimal? {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .currency
let number = numberFormatter.number(from: stringNumber)
return number?.decimalValue
}
let numbersFormatted = ["$300", "$3,000.04", "3.000,04", "Wazaa", "3000000"]
numbersFormatted
.map { changeCurrencyToDecimal($0) }
.forEach { print($0 ?? "Not a value") }
This prints:
300
3000.04
Not a value
Not a value
3000000
If you keep getting nil as a result your device's locale is different as the sample string number you are using in the question ($300).

How to force iOS Speech API to read only numbers and recognize "one" as "1"

I want to use iOS Speech API to recognize mathematical expressions. It works okay for things like two plus four times three - reads it as 2+4*3, but when I start expression with 1 it always reads it as "One". When "One" is in the middle of expression it works as expected.
I figured out that when I set SFSpeechAudioBufferRecognitionRequest property taskHint to .search when displaying live results it recognizes 1 as "1" properly at first but at the end changes it to "One"
Is there a way to configure it to recognize only numbers?
Or just force to read "One" as "1" always?
Or the only way to fix it is to format result string on my own?
I have the same problem, but looks like there is no way to configure it.
I write this extension for my code, I'm checking every segment with this.
extension String {
var numericValue: NSNumber? {
//init number formater
let numberFormater = NumberFormatter()
//check if string is numeric
numberFormater.numberStyle = .decimal
guard let number = numberFormater.number(from: self.lowercased()) else {
//check if string is spelled number
numberFormater.numberStyle = .spellOut
//change language to spanish
//numberFormater.locale = Locale(identifier: "es")
return numberFormater.number(from: self.lowercased())
}
// return converted numeric value
return number
}
}
For example
{
let numString = "1.5"
let number = numString.numericValue //1.5
// or
let numString = "Seven"
let number = numString.numericValue //7
}

Using Swift how to convert a string to a number

I am getting values back from a web service that gives me back prices in a string format, this is put into a Dictionary, so I get prices back as "1.5000" for example, which is obviously 1.50 in currency. However for the life of me I cannot get anything to work in Swift to format this correctly. In most other languages you can do this in a couple of seconds, so I'm getting a bit frustrated with something that is so simple.
Here's my test code:
var testnumber = "1.5000"
let n = NSNumberFormatter()
n.numberStyle = NSNumberFormatterStyle.DecimalStyle
n.maximumFractionDigits = 2
n.minimumFractionDigits = 2
let returnNumber = n.numberFromString(testnumber)
println("Returned number is \(returnNumber)")
This prints out in debug "number is Optional(1.5)" not 1.50!
I have changed NSNumberFormatterStyle.DecimalStyle to NSNumberFormatterStyle.CurrencyStyle as I thought that may do it for me as the returned number is a currency anyway, but that gives me back in debug "Returned number is nil" - which is even more confusing to me!
I have tried using maximumIntegerDigits and minimumIntegerDigits, setting locales using n.locale = NSLocale.currentLocale(), setting formatWidth, setting paddingPosition and paddingCharacter but nothing helps, I either get nil back to 1.5.
All I ultimately need to do is convert a string to a float or a currency value, and ensure there are 2 decimal places, and I can't believe it's this hard to accomplish!
Any help would be very gratefully received.
You are printing a number not a string
Xcode 11.4 • Swift 5.2 or later
extension Formatter {
static let usCurrency: NumberFormatter = {
let formatter = NumberFormatter()
formatter.locale = .init(identifier: "en_US")
formatter.numberStyle = .currency
return formatter
}()
}
extension String {
var double: Double? { Double(self) }
var usCurrencyFormatted: String {
Formatter.usCurrency.string(for: double) ?? Formatter.usCurrency.string(for: 0) ?? ""
}
}
"1.1222".usCurrencyFormatted // "$1.12"
"2".usCurrencyFormatted // "$2.00"
The problem is about numberFromString returning an optional - so you have to unwrap before printing. Just to be safe, you can use optional binding:
if let returnNumber = n.numberFromString(testnumber) {
println("Returned number is \(returnNumber)")
}
otherwise if it's ok for the app to crash if the optional is nil (in some cases this is a wanted behavior if the optional is expected to always contain a non nil value) just use forced unwrapping:
let returnNumber = n.numberFromString(testnumber)!
println("Returned number is \(returnNumber)")
That fixes the unwanted "Optional(xx)" text. As for formatting a float/double number, there are probably several ways of doing it - the one I would use is c-like string formatting, available via NSString:
let formattedNumber = NSString(format: "%.2f", returnNumber)
println("Returned number is \(formattedNumber)")
Use String Format Specifiers as reference if you want to know more about format specifiers.
You could probably just use the NSNumberFormatter that you just created.
let returnNumber = n.stringFromNumber(n.numberFromString(testnumber))
returnNumber will now be of type String.
The following returns to 2 decimal places for me in playgrounds. May be of some help to you. Uses NSNumberFormatter and then unwraps the optional
let testnumber: String = "1.50000"
let numberFormatter = NSNumberFormatter()
let number = numberFormatter.numberFromString(testnumber)
if let final = number?.floatValue {
println("Returned number is " + String(format: "%.2f", final))
}

Resources