如何通过NumberFormatter以辅币单位显示SKProduct本地化小额单价?
Absolutely! You can pull this off with NumberFormatter—it just takes a bit of targeted configuration to handle both localized currency formatting and the switch to sub-unit display when the unit price is less than one main currency unit. Let me break down exactly how to implement this for your SKProduct bundles.
Step 1: Calculate the Unit Price First
First, we need to compute the individual unit price by dividing the SKProduct's total price by the number of items in the bundle. Using NSDecimalNumber here is crucial to avoid floating-point precision errors with currency values.
func calculateUnitPrice(for product: SKProduct, bundleItemCount: Int) -> NSDecimalNumber { guard bundleItemCount > 0 else { return product.price } return product.price.dividing(by: NSDecimalNumber(value: bundleItemCount)) }
Step 2: Configure NumberFormatter for Localized Display
Next, we'll set up a NumberFormatter that uses the product's priceLocale (critical for matching the user's regional currency preferences) and adds logic to switch between main currency units and sub-units (like pence or cents) when needed.
Here's the full implementation with comments explaining each part:
func formattedUnitPrice(for product: SKProduct, bundleItemCount: Int) -> String? { let unitPrice = calculateUnitPrice(for: product, bundleItemCount: bundleItemCount) let formatter = NumberFormatter() formatter.locale = product.priceLocale // Get the number of decimal digits used by the currency (most use 2, some like JPY use 0) guard let decimalDigits = formatter.locale.object(forKey: .currencyDecimalDigits) as? Int else { // Fallback to basic currency formatting if we can't get decimal digits formatter.numberStyle = .currency return formatter.string(from: unitPrice) } let mainUnitToSubUnitRatio = NSDecimalNumber(decimal: pow(10, decimalDigits)) let isUnitPriceLessThanMainUnit = unitPrice.compare(NSDecimalNumber.one) == .orderedAscending if isUnitPriceLessThanMainUnit { // Convert the unit price to an integer sub-unit amount (e.g., 0.99 GBP → 99 pence) guard let subUnitAmount = unitPrice.multiplying(by: mainUnitToSubUnitRatio).integerValue as NSNumber? else { return formatter.string(from: unitPrice) } // Map common currency codes to their sub-unit symbols for accuracy let subCurrencySymbols: [String: String] = [ "GBP": "p", "USD": "¢", "EUR": "¢", "CAD": "¢", "AUD": "¢", // Add more currencies as needed for your app ] // Use the matching sub-unit symbol, or fall back to a reasonable default if let currencyCode = formatter.locale.currencyCode, let subSymbol = subCurrencySymbols[currencyCode] { return "\(subUnitAmount)\(subSymbol)" } else { // Fallback for less common currencies: append lowercase main symbol or just the number return "\(subUnitAmount)\(formatter.currencySymbol?.lowercased() ?? "")" } } else { // For prices ≥ 1 main unit, use standard localized currency formatting formatter.numberStyle = .currency return formatter.string(from: unitPrice) } }
Key Notes & Edge Cases
- Precision Safety: Using
NSDecimalNumberensures we don't get rounding errors that come with floating-point types likeDoublewhen dividing currency values. - Locale Compatibility: By using the SKProduct's
priceLocale, we automatically align with the user's regional settings (e.g., decimal separators, currency symbols). - Zero-Decimal Currencies: For currencies like Japanese Yen (JPY) that don't use sub-units, the logic will skip the sub-unit check and always use standard currency formatting (since you can't have a price less than 1 JPY for a valid SKProduct).
- Customization: The
subCurrencySymbolsdictionary can be extended to cover all currencies your app supports, ensuring the most accurate sub-unit symbols for each region.
Example Results
- For a £9.99 bundle of 10 items (UK locale): Returns
99p - For a $9.99 bundle of 10 items (US locale): Returns
99¢ - For a $5.00 bundle of 4 items (US locale): Returns
$1.25
内容的提问来源于stack exchange,提问作者Ryan Brodie

