NumberFormatter 货币格式化属性详解

深入理解代替单纯记忆

前面分享了iOS IAP 本地货币展示:从一个需求到搞清楚 priceLocaleNumberFormatter部分比较重要,单独开一篇进行记录

一、一句话总模型

numberStyle 决定走哪条格式管道currencyCode 决定是什么钱(货币身份)currencySymbol 决定符号长什么样(仅 .currency 管道)internationalCurrencySymbol 决定 ISO 管道显示什么标识


二、numberStyle:管道选择器

numberStyle 是最关键的属性,决定底层走哪条 ICU 格式化路径。

.currency

面向用户 UI 的本地化货币格式,输出「货币符号 + 数字」组合。货币符号由 locale 推导(如 en_US$),currencyCode 可显式覆盖 locale 推导的货币身份,currencySymbol 可进一步覆盖最终显示的符号字符串;符号位置、小数位、分组分隔符均由 locale 决定。

locale 输出示例 说明
en_US $1,000.00 符号在左,点号小数
de_DE 1.000,00 € 符号在右,逗号小数
ja_JP ¥1,000 无小数位(JPY 规范)
ar_SA ر.س. 1,000.00 符号在左,阿拉伯数字格式

适用场景:面向用户的价格展示(应用内购、充值、价格标签等)。


.currencyISOCode

为什么需要它? .currency 使用的货币符号(如 $)存在天然歧义------$ 同时代表美元、加元、澳元、港元等数十种货币,在跨区域的日志、数据上报或金融报表场景中,仅凭符号无法准确识别货币种类。.currencyISOCode 改用 ISO 4217 标准代码(如 USDCADHKD),全球唯一、无歧义。

输出「ISO 4217 货币代码 + 数字」。代码由 locale 推导(与 .currency 一致),currencyCode 可显式覆盖 locale 推导值,internationalCurrencySymbol 可进一步覆盖最终显示的字符串。

locale 输出示例 说明
en_US USD 1,000.00 代码在左
de_DE 1.000,00 EUR 代码在右(跟随 locale 习惯)
ja_JP JPY 1,000 无小数位

适用场景:金融报表、服务端日志、数据上报等需要「无歧义货币标识」的场合。


.currencyPlural

输出「数字 + 货币全称(自然语言)」,遵循目标语言的单复数规则。

locale + 数值 输出示例 说明
en_US, 1.00 1.00 US dollar 单数
en_US, 2.00 2.00 US dollars 复数
zh_CN, 1.00 1.00人民币 中文无单复数变化

适用场景:需要「读出来」的场景(如语音播报、无障碍文本)。日常 UI 展示几乎不使用。


三、核心属性说明

currencyCode:货币身份(数据源)

决定「这是哪种钱」,是数据源,不是显示配置。

  • 确定货币单位(USD / JPY / EUR 等)
  • 决定默认 currencySymbol$ / ¥ /
  • 决定货币专属小数位数(JPY=0,KWD=3,USD=2)
  • 未显式设置时从 locale 自动推导

currencySymbol:符号展示(UI 层 override)

只在 .currency 管道生效,属于视觉层覆盖,不影响货币语义。

  • .currency → 生效;未设置时由 locale 自动推导
  • .currencyISOCode → 无效,被忽略

覆盖示例:

ini 复制代码
let f = NumberFormatter()
f.numberStyle = .currency
f.locale = Locale(identifier: "en_US")
// 默认:$1,000.00

f.currencySymbol = "US$"
// 覆盖后:US$1,000.00(常用于区分美元与其他 $ 货币)

f.currencySymbol = "💰"
// 覆盖后:💰1,000.00

internationalCurrencySymbol:ISO 管道专用标识

只在 .currencyISOCode 管道生效,属于标识层覆盖,不走 symbol 体系。

  • .currencyISOCode → 生效;未设置时直接使用 currencyCode 的值(如 USD
  • .currency → 无效,被忽略

覆盖示例:

ini 复制代码
let f = NumberFormatter()
f.numberStyle = .currencyISOCode
f.locale = Locale(identifier: "en_US")
f.currencyCode = "USD"
// 默认:USD 1,000.00

f.internationalCurrencySymbol = "美元"
// 覆盖后:美元 1,000.00

四、三条管道关系图

flowchart TD A[numberStyle] -->|.currency| B[Currency Pipeline] A -->|.currencyISOCode| C[ISO Pipeline] A -->|.currencyPlural| D[Plural Pipeline] %% ======================= %% currency pipeline %% ======================= B --> E[currencyCode] E -->|if not set| E1[locale 推导 currencyCode] B --> F[currencySymbol - local symbol] F --> G[ICU number formatting] %% ======================= %% ISO pipeline %% ======================= C --> H[currencyCode] H -->|if not set| H1[locale 推导 currencyCode] C --> I[internationalCurrencySymbol] I --> G %% ======================= %% plural pipeline %% ======================= D --> J[currencyCode] J -->|if not set| J1[locale 推导 currencyCode] D --> K[locale plural rules] K --> G %% ======================= %% shared engine %% ======================= G --> L[locale number formatting] L --> M[final output string]

五、验证代码与输出结果

ini 复制代码
func printTest(_ label: String, _ f: NumberFormatter) {
    print("---- (label) ----")
    print(f.string(from: 1000) ?? "nil")
}

// 测试 1:默认 currency
let f1 = NumberFormatter()
f1.numberStyle = .currency
f1.locale = Locale(identifier: "en_US")
f1.currencyCode = "USD"  // locale 已能推导出 USD,此行与不设置效果相同
printTest("Default currency", f1)
// $1,000.00

// 测试 2:覆盖 currencySymbol
let f2 = NumberFormatter()
f2.numberStyle = .currency
f2.locale = Locale(identifier: "en_US")
f2.currencyCode = "USD"  // 同上,此行与不设置效果相同
f2.currencySymbol = "💰"
printTest("Override currencySymbol", f2)
// 💰1,000.00

// 测试 3:ISO style + 覆盖 internationalCurrencySymbol
let f3 = NumberFormatter()
f3.numberStyle = .currencyISOCode
f3.locale = Locale(identifier: "en_US")
f3.currencyCode = "USD"
f3.currencySymbol = "💰"  // 被忽略
f3.internationalCurrencySymbol = "US_DOLLAR"
printTest("ISO style + override symbols", f3)
// US_DOLLAR 1,000.00

结论:

  • 测试 2 验证 currencySymbol.currency 管道生效
  • 测试 3 验证 currencySymbol.currencyISOCode 管道被忽略,internationalCurrencySymbol 才生效

六、工程实践结论

多区域内购场景的正确姿势

ini 复制代码
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = priceLocale   // 来自 SKProduct.priceLocale

为什么用 .currency 而不是 .currencyISOCode:

内购价格展示的受众是用户,用户习惯看货币符号($¥),ISO 代码(USDEURJPY)是机器/金融领域的表达方式,放在 UI 上不够直观。.currencyISOCode 适合日志、数据上报等对歧义敏感、不面向用户的场合。

只设置这两个属性,原因:

  1. SKProduct.priceLocale 已经将 localecurrencyCode 对齐(Apple 保证)
  2. currencyCodelocale 自动推导,无需手动设置
  3. 手动设置 currencyCode 但不同步 locale,会导致「格式规则与货币符号不匹配」------例如 locale = ar_SAcurrencyCode = USD,输出既非标准美元也非标准里亚尔
  4. currencySymbol 同理,修改后破坏 locale 一致性,不适合多区域场景
相关推荐
for_ever_love__5 小时前
UI学习:数据驱动ce l l
学习·ui·ios·objective-c
KillerNoBlood5 小时前
2026移动端跨平台开发面经总结
android·算法·flutter·ios·移动开发·鸿蒙·kmp
人月神话-Lee6 小时前
【图像处理】颜色科学与灰度化——人眼看到的和数字记录的不一样
图像处理·人工智能·计算机视觉·ios·swift
号码认证服务7 小时前
给用户打电话,怎么在对方手机显示为“XX证券”?号码认证办理步骤
android·运维·服务器·ios·智能手机·iphone·webview
MonkeyKing7 小时前
iOS 启动优化实战:pre-main耗时、二进制重排与动态库裁剪全解析
ios
MonkeyKing7 小时前
iOS 卡顿优化实战:离屏渲染、混合图层与圆角优化全解析
ios
库奇噜啦呼9 小时前
【iOS】源码学习-消息流程分析
学习·ios·cocoa
2501_915918419 小时前
iOS性能数据监控:从概念到工具实践,让应用运行更流畅
android·macos·ios·小程序·uni-app·cocoa·iphone
aiopencode1 天前
iOS开发中Xcode安装不完整问题解决方案与配置指南
后端·ios