Ryan Brodie
Ryan Brodie

Reputation: 6620

NumberFormatter string as minor currency unit not major with locale (e.g 99p not £0.99)

We use this function to convert an SKProduct into a localised price per item for our consumable bundles:

static func pricePerUnit(_ product: SKProduct, quantity: NSDecimalNumber) -> String? {
    let numberFormatter = NumberFormatter()
    numberFormatter.numberStyle = .currency
    numberFormatter.locale = product.priceLocale

    let pricePerItem = product.price.dividing(by: quantity)

    guard let formattedPricePerItem = numberFormatter.string(from: pricePerItem) else {
        return nil
    }

    return "\(formattedPricePerItem) each"
}

For example, a bundle of 10 items for £9.99 becomes £0.99 for a UK user or $0.99 for a US user.

Ideally, if the amount is less than a single unit of the currency (aka one dollar), we'd like it to display with the minor currency unit (such as cents, pence, etc.).

I can't find a NumberFormatter style for this or any answer elsewhere. Can this be done using NumberFormatter?

Upvotes: 1

Views: 1034

Answers (2)

Mike Henderson
Mike Henderson

Reputation: 2132

Formatting as cent and pence may not be directly supported by NumberFormatter but NumberFormatter is flexible enough so that you can configure it to your wishes. For example, one can create a helper struct that holds 2 NumberFormatter: one to format it in dollars and pounds, and one to format it in cent and pence.

struct CurrencyFormatter {
    private static let defaultFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        return formatter
    }()

    private static let alternativeFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.multiplier = 100
        return formatter
    }()

    static var alternativeCurrencySymbols = [
        "USD": "c",
        "GBP": "p"
    ]

    static func string(from number: NSNumber, locale: Locale) -> String? {
        var formatter = defaultFormatter

        if number.isLessThan(1),
            let currencyCode = locale.currencyCode,
            let alternativeCurrencySymbol = alternativeCurrencySymbols[currencyCode]
        {
            formatter = alternativeFormatter
            formatter.positiveSuffix = alternativeCurrencySymbol
            formatter.negativeSuffix = alternativeCurrencySymbol
        }

        formatter.locale = locale
        return formatter.string(from: number)
    }
}

let number = NSNumber(value: 0.7)
let locale = Locale(identifier: "en_GB")
if let str = CurrencyFormatter.string(from: number, locale: locale) {
    print(str) // result: 70p
}

The solution has a built-in fail safe mechanism. If you haven't defined the alternative symbol for a currency, it will fall back to the default currency format. You can test it by changing the locale to fr_FR and the result becomes 0,70 €.

Upvotes: 1

Sulthan
Sulthan

Reputation: 130072

Unfortunately, this cannot be done using NumberFormatter. NumberFormatter uses Locale to get currency format, including the number of decimal digits, positive patterns, negative patterns, currency symbols etc.

(see Number Format Patterns in Unicode)

However, the format for minor currencies is not standardized in Unicode (although some necessary data are, e.g. multiplicators) and it's not present in iOS Locale data.

Upvotes: 2

Related Questions