How to format a Double into Currency - Swift 3
Asked Answered
M

12

91

I'm new to Swift programming and I've been creating a simple tip calculator app in Xcode 8.2, I have my calculations set up within my IBAction below. But when I actually run my app and input an amount to calculate (such as 23.45), it comes up with more than 2 decimal places. How do I format it to .currency in this case?

@IBAction func calculateButtonTapped(_ sender: Any) {

    var tipPercentage: Double {

        if tipAmountSegmentedControl.selectedSegmentIndex == 0 {
            return 0.05
        } else if tipAmountSegmentedControl.selectedSegmentIndex == 1 {
            return 0.10
        } else {
            return 0.2
        }
    }

    let billAmount: Double? = Double(userInputTextField.text!)

    if let billAmount = billAmount {
        let tipAmount = billAmount * tipPercentage
        let totalBillAmount = billAmount + tipAmount

        tipAmountLabel.text = "Tip Amount: $\(tipAmount)"
        totalBillAmountLabel.text = "Total Bill Amount: $\(totalBillAmount)"
    }
}
Mouthwash answered 9/1, 2017 at 23:39 Comment(0)
I
159

You can use this string initializer if you want to force the currency to $:

String(format: "Tip Amount: $%.02f", tipAmount)

If you want it to be fully dependent on the locale settings of the device, you should use a NumberFormatter. This will take into account the number of decimal places for the currency as well as positioning the currency symbol correctly. E.g. the double value 2.4 will return "2,40 €" for the es_ES locale and "¥ 2" for the jp_JP locale.

let formatter = NumberFormatter()
formatter.locale = Locale.current // Change this to another locale if you want to force a specific locale, otherwise this is redundant as the current locale is the default already
formatter.numberStyle = .currency
if let formattedTipAmount = formatter.string(from: tipAmount as NSNumber) {
    tipAmountLabel.text = "Tip Amount: \(formattedTipAmount)"
}
Impostor answered 9/1, 2017 at 23:49 Comment(2)
FYI - no need to set the formatter's locale to Locale.current since that is the default.Bushing
Yeah, I know, I've added a comment to say it's redundant. Wanted to keep it in there to show it can easily be changed.Impostor
F
46

How to do it in Swift 4:

let myDouble = 9999.99
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: NSNumber(value: myDouble))!
print(priceString) // Displays $9,999.99 in the US locale
Foochow answered 4/7, 2018 at 16:51 Comment(0)
E
31

As of Swift 5.5, you can do this with the help of .formatted:

import Foundation

let amount = 12345678.9
print(amount.formatted(.currency(code: "USD")))
// prints: $12,345,678.90

This should support most common currency code, such as "EUR", "GBP", or "CNY".

Similarly, you can append locale to .currency:

print(amount.formatted(
    .currency(code:"EUR").locale(Locale(identifier: "fr-FR"))
))
// prints: 12 345 678,90 €
Embattled answered 13/2, 2022 at 17:41 Comment(3)
This solution is so much simpler than all the other answers! I can't believe I didn't know about this until now!Russell
great however availlable on ios 15 onlyStellastellar
TIL. Thanks! Such a useful command that I never realized existed.Susette
Y
24

You can to convert like that: this func convert keep for you maximumFractionDigits whenever you want to do

static func df2so(_ price: Double) -> String{
        let numberFormatter = NumberFormatter()
        numberFormatter.groupingSeparator = ","
        numberFormatter.groupingSize = 3
        numberFormatter.usesGroupingSeparator = true
        numberFormatter.decimalSeparator = "."
        numberFormatter.numberStyle = .decimal
        numberFormatter.maximumFractionDigits = 2
        return numberFormatter.string(from: price as NSNumber)!
    } 

i create it in class Model then when you call , you can accecpt it another class , like this

 print("InitData: result convert string " + Model.df2so(1008977.72))
//InitData: result convert string "1,008,977.72"
Ylem answered 23/5, 2018 at 2:40 Comment(1)
if you want to set separator in every 3 digit so remove the number style from the above code.Celebrate
C
13

you can create an Extension for either string or Int, I would show an example with String

extension String{
     func toCurrencyFormat() -> String {
        if let intValue = Int(self){
           let numberFormatter = NumberFormatter()
           numberFormatter.locale = Locale(identifier: "ig_NG")/* Using Nigeria's Naira here or you can use Locale.current to get current locale, please change to your locale, link below to get all locale identifier.*/ 
           numberFormatter.numberStyle = NumberFormatter.Style.currency
           return numberFormatter.string(from: NSNumber(value: intValue)) ?? ""
      }
    return ""
  }
}

link to get all locale identifier

Catafalque answered 3/10, 2018 at 7:28 Comment(1)
You can also use Locale.current to get the current device's locale.Muldrow
C
12

The best way to do this is to create an NSNumberFormatter. (NumberFormatter in Swift 3.) You can request currency and it will set up the string to follow the user's localization settings, which is useful.

As an alternative to using a NumberFormatter, If you want to force a US-formatted dollars and cents string you can format it this way:

let amount: Double = 123.45

let amountString = String(format: "$%.02f", amount)
Croze answered 9/1, 2017 at 23:49 Comment(0)
D
11

In addition to the NumberFormatter or String(format:) discussed by others, you might want to consider using Decimal or NSDecimalNumber and control the rounding yourself, thereby avoid floating point issues. If you're doing a simple tip calculator, that probably isn't necessary. But if you're doing something like adding up the tips at the end of the day, if you don't round the numbers and/or do your math using decimal numbers, you can introduce errors.

So, go ahead and configure your formatter:

let formatter: NumberFormatter = {
    let _formatter = NumberFormatter()
    _formatter.numberStyle = .decimal
    _formatter.minimumFractionDigits = 2
    _formatter.maximumFractionDigits = 2
    _formatter.generatesDecimalNumbers = true
    return _formatter
}()

and then, use decimal numbers:

let string = "2.03"
let tipRate = Decimal(sign: .plus, exponent: -3, significand: 125) // 12.5%
guard let billAmount = formatter.number(from: string) as? Decimal else { return }
let tip = (billAmount * tipRate).rounded(2)

guard let output = formatter.string(from: tip as NSDecimalNumber) else { return }
print("\(output)")

Where

extension Decimal {

    /// Round `Decimal` number to certain number of decimal places.
    ///
    /// - Parameters:
    ///   - scale: How many decimal places.
    ///   - roundingMode: How should number be rounded. Defaults to `.plain`.
    /// - Returns: The new rounded number.

    func rounded(_ scale: Int, roundingMode: RoundingMode = .plain) -> Decimal {
        var value = self
        var result: Decimal = 0
        NSDecimalRound(&result, &value, scale, roundingMode)
        return result
    }
}

Obviously, you can replace all the above "2 decimal place" references with whatever number is appropriate for the currency you are using (or possibly use a variable for the number of decimal places).

Doorknob answered 10/1, 2017 at 0:16 Comment(5)
Why not use the currency style for the NumberFormatter? Not all currencies use 2 decimal places.Bushing
Yep, you can do that. And you can even then look up the number of decimal places to which you should round. Admittedly, using .currency introduces other issues (e.g. the parsing of strings becomes persnickety; it assumes you're not traveling and dealing with some other currency, etc.). Depending upon the app, sometimes it's easier to let the user specify decimal places and be done with it. Besides, my point wasn't the formatter, but rather the general counsel to avoid floating point math with currencies.Doorknob
Another approach to the rounding problem is to multiply the "dollars and cents" (or euros, or shekels or whatever) amount by 100 (or by the number of fractional units in the whole unit, for currencies that don't have 100 cents). Then use integer math, and simply format the output manually to insert a decimal separator. That approach also avoids floating point errors.Croze
Well, Decimal is a lot more complex than simple integersinteger. It represents decimal values using BCD, and does math one decimal digit at a time rather than using binary math and converting. It also supports fractional values. As a result it is a LOT slower than binary floating point or integer math. For simple calculations the difference in speed doesn't matter, but if you're doing large numbers of calculations the difference can be quite significant.Croze
I agree that decimals can be less efficient (though not observably so for the vast majority of applications), but IMHO, it's the right abstraction for the question of "how to do math with currencies.”Doorknob
H
4
 extension String{
    func convertDoubleToCurrency() -> String{
        let amount1 = Double(self)
        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = .currency
        numberFormatter.locale = Locale(identifier: "en_US")
        return numberFormatter.string(from: NSNumber(value: amount1!))!
    }
}
Hasheem answered 18/3, 2020 at 11:49 Comment(0)
M
1

Here's an easy way I've been going about it.

extension String {
    func toCurrency(Amount: NSNumber) -> String {
        var currencyFormatter = NumberFormatter()
        currencyFormatter.usesGroupingSeparator = true
        currencyFormatter.numberStyle = .currency
        currencyFormatter.locale = Locale.current

        return currencyFormatter.string(from: Amount)!
    }
}

Being used as follows

let amountToCurrency = NSNumber(99.99)
String().toCurrency(Amount: amountToCurrency)
Monamonachal answered 12/11, 2021 at 3:28 Comment(0)
C
1

In 2022 using Swift 5.5, I created extensions that convert Float or Double into a currency using your device's locale or the locale you pass as an argument. You can check it out here https://github.com/ahenqs/SwiftExtensions/blob/main/Currency.playground/Contents.swift

import UIKit

extension NSNumber {
    
    /// Converts an NSNumber into a formatted currency string, device's current Locale.
    var currency: String {
        return self.currency(for: Locale.current)
    }
    
    /// Converts an NSNumber into a formatted currency string, using Locale as a parameter.
    func currency(for locale: Locale) -> String {
        let numberFormatter = NumberFormatter()
        numberFormatter.usesGroupingSeparator = locale.groupingSeparator != nil
        numberFormatter.numberStyle = .currency
        numberFormatter.locale = locale
        
        return numberFormatter.string(from: self)!
    }
}

extension Double {
    
    /// Converts a Double into a formatted currency string, device's current Locale.
    var currency: String {
        return NSNumber(value: self).currency(for: Locale.current)
    }
    
    /// Converts a Double into a formatted currency string, using Locale as a parameter.
    func currency(for locale: Locale) -> String {
        return NSNumber(value: self).currency(for: locale)
    }
}

extension Float {
    
    /// Converts a Float into a formatted currency string, device's current Locale.
    var currency: String {
        return NSNumber(value: self).currency(for: Locale.current)
    }
    
    /// Converts a Float into a formatted currency string, using Locale as a parameter.
    func currency(for locale: Locale) -> String {
        return NSNumber(value: self).currency(for: locale)
    }
}

let amount = 3927.75 // Can be either Double or Float, since we have both extensions.
let usLocale = Locale(identifier: "en-US") // US
let brLocale = Locale(identifier: "pt-BR") // Brazil
let frLocale = Locale(identifier: "fr-FR") // France
print("\(Locale.current.identifier) -> " + amount.currency) // default current device's Locale.
print("\(usLocale.identifier) -> " + amount.currency(for: usLocale))
print("\(brLocale.identifier) -> " + amount.currency(for: brLocale))
print("\(frLocale.identifier) -> " + amount.currency(for: frLocale))

// will print something like this:
// en_US -> $3,927.75
// en-US -> $3,927.75
// pt-BR -> R$ 3.927,75
// fr-FR -> 3 927,75 €

I hope it helps, happy coding!

Comeback answered 14/5, 2022 at 11:56 Comment(0)
B
0
extension Float {
    var localeCurrency: String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.locale = .current
        return formatter.string(from: self as NSNumber)!
    }
}
    amount = 200.02
    print("Amount Saved Value ",String(format:"%.2f", amountSaving. localeCurrency))

For me Its return 0.00! Looks to me Extenstion Perfect when accessing it return 0.00! Why?

Butts answered 14/7, 2019 at 23:11 Comment(5)
localeCurrency is already a string. Why are you trying to feed it through a string initializer?Mycah
So why are you doing that?Mycah
I did not check that return type properly!Butts
So are you going to fix it?Mycah
It's still there... String(format:"%.2f", amountSaving. localeCurrency)Mycah
A
-5

Here's how:

    let currentLocale = Locale.current
    let currencySymbol = currentLocale.currencySymbol
    let outputString = "\(currencySymbol)\(String(format: "%.2f", totalBillAmount))"

1st line: You're getting the current locale

2nd line: You're getting the currencySymbol for that locale. ($, £, etc)

3rd line: Using the format initializer to truncate your Double to 2 decimal places.

Alisander answered 9/1, 2017 at 23:55 Comment(2)
Do not do this. Use NumberFormatter. This approach has many problems that are easily avoided by using NumberFormatter.Bushing
Downvoted because for example, we write "10,00 €", not "€10.00". Depending on the locale, prices are not written the same way :/Illfounded

© 2022 - 2024 — McMap. All rights reserved.