好用的 NumberFormatter

做電商在顯示價錢的時候往往要考量到貨幣類型(e.g. TWD, EUR)、貨幣符號(e.g. $, ¥, €)、千分位等等因素,在業界不知為何蠻常看到有人自己在組字串或判斷千分位,其實這些事情可以交給 NumberFormatter 來做。

 

單純顯示 $$

先寫一個在台灣適用的最簡單例子,因為台幣沒有小數點,所以貨幣的數值後台常常會直接傳 Int 回來,我們就可以對 Int 寫一個 Extension:

extension Int {
    func currency(showSymbol: Bool = true) -> String? {
        let number = NSNumber(value: self)
        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = .currency
        numberFormatter.currencySymbol = showSymbol ? "$" : "" // note 1
        numberFormatter.maximumFractionDigits = 0 // note 2
        
        return numberFormatter.string(from: number)
    }
}

Note

  1. 其實只要設定 numberStyle = .currency 回傳的字串就會帶錢字號”$”,這邊會特別去做處理是因為有時候我不需要那個錢字號
  1. maximumFractionDigits 指的是要顯示小數點後幾位,台幣顯示就會指定為 0。

在使用時只要在整數後面呼叫 .currency() 就可以拿到帶有錢字號即千分位的貨幣字串。

print("currency: \(String(describing: 1920.currency()))")
// output => currency: Optional("$1,920")

指定貨幣代碼

我們也可以將貨幣代碼(currency code)傳給 number formatter。

這邊提一下若是有小數點的貨幣,應該改用 Decimal data type 以確保其後續運算不會因為浮點數的特性而導致計算失準,對浮點數有興趣可以參考 iPlayground 的這則分享

extension Decimal {
    func currency(currencyCode: String) -> String? {
        let number = NSDecimalNumber(decimal: self) // note 1
        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = .currency // note 2
        numberFormatter.currencyCode = currencyCode
        numberFormatter.maximumFractionDigits = 2
        
        return numberFormatter.string(from: number)
    }
}

print("currency: \(String(describing: Decimal(1920).currency(currencyCode: "EUR")))")
// output => currency: Optional("€1,920.00")

Note

  1. Decimal 型別需轉成 NSDecimalNumber 而非 NSNumber
  1. numberStyle 除了 .currency 以外還有包含 .currencyISOCode 及 .currencyPlural, .currencyAccounting 等等可以選擇
    1. .currency: €1,920.00
    1. .currencyISOCode: EUR 1920.00
    2. .currencyAccounting: 若是台幣(TWD)會顯示 NT$1920,歐元則保持 €1,920.00
    3. .currencyPlural: 目前測試都會回傳 1920.00 US dollars

 

支援貨幣在地化

最後,我們也可以指定 local 給 NumberFormatter 達到在地化的需求:

let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale.current
...