使用Swift从货币代码获取货币符号

39

如何使用Swift(macOS)获取对应货币代码的货币符号。

示例:

  • EUR = €1.00
  • USD = $1.00
  • CAD = $1.00
  • GBP = £1.00

我的代码:

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

还有... 是否有更好的方法吗?

12个回答

45

有点晚了,但这是我用来获取货币符号$而不是US$等的解决方案。

/*
 * 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)
}

基本上,这将从您的货币代码创建NSLocale并获取货币的显示属性。在结果匹配货币代码(例如SEK)的情况下,它将通过从货币代码中删除最后一个字符并添加“_en”来形成SE_en,创建新的特定于国家/地区的语言环境。然后再次尝试获取货币符号。

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)
}

I get CA$ instead of $. - Etienne Martin
你使用的是Swift 2、Swift 3还是ObjC? - Pancho
2
@Pancho 对于SEK,我得到的是SEK而不是kr,可能出了什么问题?对于DKK也是一样。 - Gaurav Borole
@GauravBorole 看起来地区设置不正确。请查看我的更新答案。 - Pancho
1
我知道这是一个古老的答案,但是这段代码对我来说看起来不对。 NSLocale(localeIdentifier :) 的参数应该是一个本地标识符,通常由ISO 639-1语言代码(例如英语为en)和ISO 3166-2区域代码(例如美国为US)组成。但是在上面的代码中将货币代码传递给了该函数。这很奇怪。 - rayx
显示剩余3条评论

16

答案可能会晚一些,但希望这有助于澄清根本原因。

问题

  • 货币代码不涵盖地区信息

CAD变成CA$的原因可能是因为NSLocale在查找第一个匹配的货币代码时,对于CAD而言,以下是按照NSLocale.availableLocaleIdentifiers顺序排序的匹配的localeIdentifiers

1. Optional("CA$") Optional("CA") iu_CA
2. Optional("$") Optional("CA") fr_CA
3. Optional("$") Optional("CA") en_CA

iu_CA是伊努克提图特语,但我不确定为什么它被列为CA$,但我希望重点是清楚的。

同样,在CNY(人民币)中:

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
在 en_CN 显示 CN¥ 的原因可能是因为 JPY 也使用 ¥。
在 CHF(瑞士法郎)中,他们没有一个字母符号:
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

解决方案

虽然应用程序各不相同,但这是我为自己的应用程序采取的步骤:

  1. 使用所有语言环境标识符的货币代码查找匹配的地区候选项
  2. 从候选项中选择最短的符号
  3. 将符号存储在某个地方,以便不需要每次重新计算

实现

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 })
}

使用方法

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"

问题

  1. 本能地,我认为如果货币代码有多个不同的符号,则一个字母的符号方法可能会显示出错误的符号,但我没有见过这种情况。
  2. 每次计算都很耗费精力,因此当用户设置他们的货币设置时,明智的做法是存储计算结果并在每次查找时使用该结果。

是的,我们可以删除 guard let symbol = locale.currencySymbol else { return nil },而 sortAscByLengthn log n,但我们只需要最低字符长度的一个,所以我们可能可以删除它,并在循环中将具有最低字符长度的符号保留在变量中。我一定是累了... - Taku

14

正确的做法是让框架为您提供信息。

您可以使用NSLocale中的一个晦涩的类方法localeIdentifierFromComponents()检索该信息。该方法将使用定义区域设置的各种属性的字典作为参数,然后返回一个标识符,您可以使用它来实际构建NSLocale实例。一旦您拥有了NSLocale,您就可以询问它的CurrencySymbol,如下所示:

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$"

8
可以,但是有没有办法只显示符号而不显示 CA - Geek20
1
让 locale = NSLocale(localeIdentifier: currencyCode) 让 currencySymbol = locale.displayName(forKey: .currencySymbol, value: currencyCode) ?? currencyCode 输出("货币符号:(currencySymbol)") - Himanshu Moradiya

13

我将这里所有的建议结合并改进,以便未来读者(您)可以轻松复制/粘贴使用。

它拥有自己的本地缓存,不区分大小写,并且具有扩展方法以提供String的链接。适用于Swift 4/5。

如何使用:

"USD".currencySymbol //returns "$"
//OR
Currency.shared.findSymbol(currencyCode: "TRY") //returns "₺"

测试:

    XCTAssertEqual("$", "USD".currencySymbol)
    XCTAssertEqual("₺", "TRY".currencySymbol)
    XCTAssertEqual("€", "EUR".currencySymbol)
    XCTAssertEqual("", "ASDF".currencySymbol)

代码:

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) }
}

1
将金额与符号组合的变量:https://gist.github.com/iwasrobbed/f32c01c6965665f5823d0e0c683867e2 - iwasrobbed

9
为了在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)")

7

我建议采用更快速、更方便的方法。您可以获取特定货币的所有可能符号:

Currency.currency(for: "USD")! // Currency(code: "USD", symbols: ["US$", "USD", "$"])
Currency.currency(for: "CAD")! // Currency(code: "USD", symbols: ["$", "CA$"])

或者获得最短的符号。通常这就是大家想要的:

Currency.currency(for: "CAD")!.shortestSymbol // "$"

现在来谈一下速度问题。首次调用此方法需要线性时间,与语言环境数量和代码数量成比例。对于任何代码的每个后续调用都是在常量时间内执行的。因此,如果您正在实现一个 CurrencyPickerViewController 或类似的东西,那么这个解决方案是最优的。

此外,这个解决方案还有一个优点。由于全局常量和变量总是懒计算的,如果您从未调用过某个获得货币信息的方法,那么缓存不会占用任何内存。

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
   }()
}

要查看此功能在所有代码中的工作原理,您可以使用以下代码:

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)
}

6
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) 
}

4
我发现的一个不完美的解决方案是,首先尝试将用户当前的区域设置与货币代码匹配。这适用于构建移动应用程序且API基于该用户帐户中的设置发送货币代码的情况。对于我们的业务案例来说,99%的用户在后端设置中(如从中获取信息)和他们在移动应用程序中所期望看到的货币方式(即$50.56而不是US $50.56)相同,因此这种方法可以获得美元符号($)而不是US$或CA$。
- (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;
}

2

Swift 4版本的Pancho答案,由于现在String.characters已经弃用。

我们可以简单地在String上应用dropLast()

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)
}

2
您可以使用静态的“availableIdentifiers”集合,其中包含所有可能的标识符,如下所示:
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
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接