Reputation: 713
How can I get the currency symbols for the corresponding currency code with Swift (macOS).
Example:
My code:
var formatter = NSNumberFormatter()
formatter.currencySymbol = getSymbol(currencyCode)
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
let number = NSNumber(double: (amount as NSString).doubleValue)
let amountWithSymbol = formatter.stringFromNumber(number)!
getSymbol(_ currencyCode: String) -> String
or.. is there a better way?
Upvotes: 40
Views: 39894
Reputation: 817
I suggest a faster and more convenient solution. You can get all possible symbols for a specific currency:
Currency.currency(for: "USD")! // Currency(code: "USD", symbols: ["US$", "USD", "$"])
Currency.currency(for: "CAD")! // Currency(code: "USD", symbols: ["$", "CA$"])
Or get the shortest symbol. This is usually what everyone wants:
Currency.currency(for: "CAD")!.shortestSymbol // "$"
Now about the speed. The first call to this method takes linear time proportional to the number of locales plus the number of codes. Each subsequent call for any code is executed in constant time. Therefore, if you are implementing a CurrencyPickerViewController
or something similar, then this solution is optimal.
Also, this solution has one more plus. Since global constants and variables are always computed lazily, if you never call a method to get information about the currency, the cache will not take up any memory.
struct Currency {
/// Returns the currency code. For example USD or EUD
let code: String
/// Returns currency symbols. For example ["USD", "US$", "$"] for USD, ["RUB", "₽"] for RUB or ["₴", "UAH"] for UAH
let symbols: [String]
/// Returns shortest currency symbols. For example "$" for USD or "₽" for RUB
var shortestSymbol: String {
return symbols.min { $0.count < $1.count } ?? ""
}
/// Returns information about a currency by its code.
static func currency(for code: String) -> Currency? {
return cache[code]
}
// Global constants and variables are always computed lazily, in a similar manner to Lazy Stored Properties.
static fileprivate var cache: [String: Currency] = { () -> [String: Currency] in
var mapCurrencyCode2Symbols: [String: Set<String>] = [:]
let currencyCodes = Set(Locale.commonISOCurrencyCodes)
for localeId in Locale.availableIdentifiers {
let locale = Locale(identifier: localeId)
guard let currencyCode = locale.currencyCode, let currencySymbol = locale.currencySymbol else {
continue
}
if currencyCode.contains(currencyCode) {
mapCurrencyCode2Symbols[currencyCode, default: []].insert(currencySymbol)
}
}
var mapCurrencyCode2Currency: [String: Currency] = [:]
for (code, symbols) in mapCurrencyCode2Symbols {
mapCurrencyCode2Currency[code] = Currency(code: code, symbols: Array(symbols))
}
return mapCurrencyCode2Currency
}()
}
To see how this functionality works for all codes, you can use the code:
for code in Locale.commonISOCurrencyCodes {
guard let currency = Currency.currency(for: code) else {
// Three codes have no symbol. This is CUC, LSL and VEF
print("Missing symbols for code \(code)")
continue
}
print(currency)
}
Upvotes: 7
Reputation: 234
You can use static 'availableIdentifiers' collection, containing all posible identifiers as follows:
extension Locale {
static func locale(from currencyIdentifier: String) -> Locale? {
let allLocales = Locale.availableIdentifiers.map({ Locale.init(identifier: $0) })
return allLocales.filter({ $0.currencyCode == currencyIdentifier }).first
}
}
Upvotes: 2
Reputation: 11
let countryCode = (Locale.current as NSLocale).object(forKey: .currencySymbol) as? String ?? "$"
Above will give current currency Symbol, For UK it gives me = £ Apple Doc
Upvotes: 1
Reputation: 10738
To simply get the currency symbol in Swift.
let locale = Locale.current // currency symbol from current location
//let locale = Locale(identifier: "fr_FR") // or you could specify the locale e.g fr_FR, en_US etc
let currencySymbol = locale.currencySymbol!
print("\(currencySymbol)")
Upvotes: 12
Reputation: 7035
I combined and improved all the suggestion here to have a drop-in(copy/paste) solution for the future readers(you).
It has its own local cache, case insensitive, and have an extension method to provide chaining for String
. Swift
4/5 ready.
How to use:
"USD".currencySymbol //returns "$"
//OR
Currency.shared.findSymbol(currencyCode: "TRY") //returns "₺"
Tests:
XCTAssertEqual("$", "USD".currencySymbol)
XCTAssertEqual("₺", "TRY".currencySymbol)
XCTAssertEqual("€", "EUR".currencySymbol)
XCTAssertEqual("", "ASDF".currencySymbol)
Code:
class Currency {
static let shared: Currency = Currency()
private var cache: [String:String] = [:]
func findSymbol(currencyCode:String) -> String {
if let hit = cache[currencyCode] { return hit }
guard currencyCode.count < 4 else { return "" }
let symbol = findSymbolBy(currencyCode)
cache[currencyCode] = symbol
return symbol
}
private func findSymbolBy(_ currencyCode: String) -> String {
var candidates: [String] = []
let locales = NSLocale.availableLocaleIdentifiers
for localeId in locales {
guard let symbol = findSymbolBy(localeId, currencyCode) else { continue }
if symbol.count == 1 { return symbol }
candidates.append(symbol)
}
return candidates.sorted(by: { $0.count < $1.count }).first ?? ""
}
private func findSymbolBy(_ localeId: String, _ currencyCode: String) -> String? {
let locale = Locale(identifier: localeId)
return currencyCode.caseInsensitiveCompare(locale.currencyCode ?? "") == .orderedSame
? locale.currencySymbol : nil
}
}
extension String {
var currencySymbol: String { return Currency.shared.findSymbol(currencyCode: self) }
}
Upvotes: 13
Reputation: 4143
A bit late but this is a solution I used to get the $ instead of US$ etc. for currency symbol.
/*
* Bear in mind not every currency have a corresponding symbol.
*
* EXAMPLE TABLE
*
* currency code | Country & Currency | Currency Symbol
*
* BGN | Bulgarian lev | лв
* HRK | Croatian Kuna | kn
* CZK | Czech Koruna | Kč
* EUR | EU Euro | €
* USD | US Dollar | $
* GBP | British Pound | £
*/
func getSymbol(forCurrencyCode code: String) -> String? {
let locale = NSLocale(localeIdentifier: code)
return locale.displayNameForKey(NSLocaleCurrencySymbol, value: code)
}
Basically this creates NSLocale
from your currency code and grabs the display attribute for the currency. In cases where the result matches the currency code for example SEK
it will create new country specific locale by removing the last character from the currency code and appending "_en" to form SE_en
. Then it will try to get the currency symbol again.
Swift 3 & 4
func getSymbol(forCurrencyCode code: String) -> String? {
let locale = NSLocale(localeIdentifier: code)
if locale.displayName(forKey: .currencySymbol, value: code) == code {
let newlocale = NSLocale(localeIdentifier: code.dropLast() + "_en")
return newlocale.displayName(forKey: .currencySymbol, value: code)
}
return locale.displayName(forKey: .currencySymbol, value: code)
}
Upvotes: 46
Reputation: 5918
The answer may be late but hopefully this helps clarify the root cause.
The reason why CAD becomes CA$ is probably because NSLocale
looks up the first matching currency code, and for CAD, these are the matching localeIdentifiers
in order of NSLocale.availableLocaleIdentifiers
1. Optional("CA$") Optional("CA") iu_CA
2. Optional("$") Optional("CA") fr_CA
3. Optional("$") Optional("CA") en_CA
iu_CA
is Inuktitut but I'm not sure why it's listed as CA$
, but I hope the point is clear.
Similarly in CNY (Chinese Yuan):
1. Optional("CN¥") Optional("CN") en_CN
2. Optional("¥") Optional("CN") yue_CN
3. Optional("¥") Optional("CN") bo_CN
4. Optional("¥") Optional("CN") zh_CN
5. Optional("¥") Optional("CN") ug_CN
6. Optional("¥") Optional("CN") ii_CN
The reason for showing CN¥ when en_CN is probably because JPY also uses ¥.
In CHF (Switzerland Franc), they do not have a one-letter symbol:
1. Optional("CHF") Optional("LI") gsw_LI
2. Optional("CHF") Optional("CH") de_CH
...
9. Optional("CHF") Optional("CH") en_CH
10. Optional("CHF") Optional("CH") it_CH
Many apps vary, but this is the steps I took that I am happy with for my application:
Implementation
func getSymbolForCurrencyCode(code: String) -> String {
var candidates: [String] = []
let locales: [String] = NSLocale.availableLocaleIdentifiers
for localeID in locales {
guard let symbol = findMatchingSymbol(localeID: localeID, currencyCode: code) else {
continue
}
if symbol.count == 1 {
return symbol
}
candidates.append(symbol)
}
let sorted = sortAscByLength(list: candidates)
if sorted.count < 1 {
return ""
}
return sorted[0]
}
func findMatchingSymbol(localeID: String, currencyCode: String) -> String? {
let locale = Locale(identifier: localeID as String)
guard let code = locale.currencyCode else {
return nil
}
if code != currencyCode {
return nil
}
guard let symbol = locale.currencySymbol else {
return nil
}
return symbol
}
func sortAscByLength(list: [String]) -> [String] {
return list.sorted(by: { $0.count < $1.count })
}
Usage
let usd = getSymbolForCurrencyCode(code: "USD")
let jpy = getSymbolForCurrencyCode(code: "JPY")
let cny = getSymbolForCurrencyCode(code: "CNY")
let cad = getSymbolForCurrencyCode(code: "CAD")
let uah = getSymbolForCurrencyCode(code: "UAH")
let krw = getSymbolForCurrencyCode(code: "KRW")
let zar = getSymbolForCurrencyCode(code: "ZAR")
let chf = getSymbolForCurrencyCode(code: "CHF")
let all = [usd, jpy, cny, cad, uah, krw, zar, chf]
(lldb) po all
▿ 8 elements
- 0 : "$"
- 1 : "¥"
- 2 : "¥"
- 3 : "$"
- 4 : "₴"
- 5 : "₩"
- 6 : "R"
- 7 : "CHF"
Problems
Upvotes: 17
Reputation: 2913
Swift 4 Version of Pancho's answer, As the String.characters is deprecated now.
We can simply apply dropLast() on String.
func getCurrencySymbol(from currencyCode: String) -> String? {
let locale = NSLocale(localeIdentifier: currencyCode)
if locale.displayName(forKey: .currencySymbol, value: currencyCode) == currencyCode {
let newlocale = NSLocale(localeIdentifier: currencyCode.dropLast() + "_en")
return newlocale.displayName(forKey: .currencySymbol, value: currencyCode)
}
return locale.displayName(forKey: .currencySymbol, value: currencyCode)
}
Upvotes: 2
Reputation: 203
SWIFT4
//converting USD to $a and GBP to £
viewDidLoad()
{
print(getSymbolForCurrencyCode(code: "USD")!) // prints $
print(getSymbolForCurrencyCode(code: "GBP")!) //prints £
}
func getSymbolForCurrencyCode(code: String) -> String?
{
let locale = NSLocale(localeIdentifier: code)
return locale.displayName(forKey: NSLocale.Key.currencySymbol, value: code)
}
Upvotes: 6
Reputation: 4959
An imperfect solution I found to get $
instead of US$
or CA$
was to attempt to match the user's current locale to the currency code first. This will work for situations where you're building a mobile app and an API is sending you currency code based on the settings in that user's account. For us the business case is that 99% of users have the same currency code set in their account on the backend (USD
, CAD
, EUR
, etc.), where we're getting the information from, as they do on their mobile app where we're displaying currency the way a user would expect to see it (i.e. $50.56
instead of US$ 50.56
).
Objective-C
- (NSLocale *)localeFromCurrencyCode:(NSString *)currencyCode {
NSLocale *locale = [NSLocale currentLocale];
if (![locale.currencyCode isEqualToString:currencyCode]) {
NSDictionary *localeInfo = @{NSLocaleCurrencyCode:currencyCode};
locale = [[NSLocale alloc] initWithLocaleIdentifier:[NSLocale localeIdentifierFromComponents:localeInfo]];
}
return locale;
}
Swift
func locale(from currencyCode: String) -> Locale {
var locale = Locale.current
if (locale.currencyCode != currencyCode) {
let identifier = NSLocale.localeIdentifier(fromComponents: [NSLocale.Key.currencyCode.rawValue: currencyCode])
locale = NSLocale(localeIdentifier: identifier) as Locale
}
return locale;
}
Upvotes: 5
Reputation: 243146
The proper way to do this is to let the frameworks provide the information for you.
You can retrieve that information using an obscure class method on NSLocale
called localeIdentifierFromComponents()
. That method will take a dictionary that defines various attributes of your locale, and then returns an identifier you can use to actually construct an NSLocale
instance. Once you have the NSLocale
, you can ask it for its CurrencySymbol
, like this:
let currencyCode = "CAD"
let localeComponents = [NSLocaleCurrencyCode: currencyCode]
let localeIdentifier = NSLocale.localeIdentifierFromComponents(localeComponents)
let locale = NSLocale(localeIdentifier: localeIdentifier)
let currencySymbol = locale.objectForKey(NSLocaleCurrencySymbol) as! String
// currencySymbol is "CA$"
Upvotes: 14
Reputation: 11555
You can try this:
let formatter = NSNumberFormatter()
for locale in NSLocale.availableLocaleIdentifiers() {
formatter.locale = NSLocale(localeIdentifier: locale)
print("\(formatter.currencyCode) = \(formatter.currencySymbol)")
}
Upvotes: 1