2

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.

Dave
  • 351
  • 4
  • 18

4 Answers4

9

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"
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
1

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.

Antonio
  • 71,651
  • 11
  • 148
  • 165
  • @Dave: 1.5 and 1.50 are exactly the same numbers. – Martin R Jan 16 '15 at 20:08
  • I am aware that 1.5 and 1.50 are the same number, but it needs to be formatted correctly to the user. Who has ever seen a price give to a user as $1.5 rather than $1.50. That's all I am trying to do. – Dave Jan 16 '15 at 20:10
  • 1
    @Dave: Well, in your question you said that you want to *"... convert a string to a float"* and 1.5 is a float. If you want to present it as "$1.50" then you have to convert the floating point number back to a string with a suitable number formatter. – Martin R Jan 16 '15 at 20:13
0

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.

Ian MacDonald
  • 13,472
  • 2
  • 30
  • 51
0

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))
} 
CherryCoda
  • 53
  • 1
  • 6