swift-formatstyle

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Swift FormatStyle

Swift FormatStyle

Format values for human-readable display using the
FormatStyle
protocol and Foundation's concrete format styles. Replaces legacy
Formatter
subclasses with a type-safe, composable, cacheable API.
使用
FormatStyle
协议和Foundation的具体格式样式,将值格式化为人类可读的展示形式。它以类型安全、可组合、可缓存的API替代了旧版
Formatter
子类。
文档:FormatStyle

Contents

目录

Quick Reference

快速参考

TypeStyle AccessExample
Int
,
Double
.number
42.formatted(.number.precision(.fractionLength(2)))
"42.00"
Currency
.currency(code:)
29.99.formatted(.currency(code: "USD"))
"$29.99"
Percent
.percent
0.85.formatted(.percent)
"85%"
Date
.dateTime
Date.now.formatted(.dateTime.month().day().year())
Date range
.interval
(date1..<date2).formatted(.interval)
Relative date
.relative(presentation:unitsStyle:)
date.formatted(.relative(presentation: .named))
"yesterday"
Duration
.time(pattern:)
Duration.seconds(3661).formatted(.time(pattern: .hourMinuteSecond))
"1:01:01"
Duration
.units(allowed:width:)
Duration.seconds(90).formatted(.units(allowed: [.minutes, .seconds]))
"1 min, 30 sec"
Measurement
.measurement(width:)
Measurement(value: 72, unit: UnitTemperature.fahrenheit).formatted(.measurement(width: .abbreviated))
PersonNameComponents
.name(style:)
name.formatted(.name(style: .short))
"Tom"
[String]
.list(type:width:)
["A","B","C"].formatted(.list(type: .and))
"A, B, and C"
Byte count
.byteCount(style:)
Int64(1_048_576).formatted(.byteCount(style: .memory))
"1 MB"
URL
.url
url.formatted(.url.scheme(.never).host().path())
类型样式访问方式示例
Int
,
Double
.number
42.formatted(.number.precision(.fractionLength(2)))
"42.00"
货币
.currency(code:)
29.99.formatted(.currency(code: "USD"))
"$29.99"
百分比
.percent
0.85.formatted(.percent)
"85%"
Date
.dateTime
Date.now.formatted(.dateTime.month().day().year())
日期范围
.interval
(date1..<date2).formatted(.interval)
相对日期
.relative(presentation:unitsStyle:)
date.formatted(.relative(presentation: .named))
"yesterday"
Duration
.time(pattern:)
Duration.seconds(3661).formatted(.time(pattern: .hourMinuteSecond))
"1:01:01"
Duration
.units(allowed:width:)
Duration.seconds(90).formatted(.units(allowed: [.minutes, .seconds]))
"1 min, 30 sec"
Measurement
.measurement(width:)
Measurement(value: 72, unit: UnitTemperature.fahrenheit).formatted(.measurement(width: .abbreviated))
PersonNameComponents
.name(style:)
name.formatted(.name(style: .short))
"Tom"
[String]
.list(type:width:)
["A","B","C"].formatted(.list(type: .and))
"A, B, and C"
字节数
.byteCount(style:)
Int64(1_048_576).formatted(.byteCount(style: .memory))
"1 MB"
URL
.url
url.formatted(.url.scheme(.never).host().path())

Numbers

数字

swift
// Default locale-aware formatting
let n = 1234567.formatted()  // "1,234,567" (en_US)

// Precision
1234.5.formatted(.number.precision(.fractionLength(0...2)))  // "1,234.5"
1234.5.formatted(.number.precision(.significantDigits(3)))    // "1,230"

// Rounding
1234.formatted(.number.rounded(rule: .down, increment: 100)) // "1,200"

// Grouping
1234567.formatted(.number.grouping(.never))                   // "1234567"

// Notation
1_200_000.formatted(.number.notation(.compactName))           // "1.2M"
42.formatted(.number.notation(.scientific))                    // "4.2E1"

// Sign display
(-42).formatted(.number.sign(strategy: .always()))            // "+42" / "-42"

// Locale override
42.formatted(.number.locale(Locale(identifier: "de_DE")))     // "42"
swift
// 默认区域感知格式化
let n = 1234567.formatted()  // "1,234,567" (en_US)

// 精度设置
1234.5.formatted(.number.precision(.fractionLength(0...2)))  // "1,234.5"
1234.5.formatted(.number.precision(.significantDigits(3)))    // "1,230"

// 舍入规则
1234.formatted(.number.rounded(rule: .down, increment: 100)) // "1,200"

// 分组设置
1234567.formatted(.number.grouping(.never))                   // "1234567"

// 表示法
1_200_000.formatted(.number.notation(.compactName))           // "1.2M"
42.formatted(.number.notation(.scientific))                    // "4.2E1"

// 符号显示
(-42).formatted(.number.sign(strategy: .always()))            // "+42" / "-42"

// 区域覆盖
42.formatted(.number.locale(Locale(identifier: "de_DE")))     // "42"

Currency

货币

swift
29.99.formatted(.currency(code: "USD"))   // "$29.99"
29.99.formatted(.currency(code: "EUR"))   // "€29.99"
29.99.formatted(.currency(code: "JPY"))   // "¥30"

// Customize precision
let style = FloatingPointFormatStyle<Double>.Currency(code: "USD")
    .precision(.fractionLength(0))
1234.56.formatted(style)  // "$1,235"
swift
29.99.formatted(.currency(code: "USD"))   // "$29.99"
29.99.formatted(.currency(code: "EUR"))   // "€29.99"
29.99.formatted(.currency(code: "JPY"))   // "¥30"

// 自定义精度
let style = FloatingPointFormatStyle<Double>.Currency(code: "USD")
    .precision(.fractionLength(0))
1234.56.formatted(style)  // "$1,235"

Percentages

百分比

swift
0.85.formatted(.percent)                                      // "85%"
0.8567.formatted(.percent.precision(.fractionLength(1)))       // "85.7%"
42.formatted(.percent)                                         // "42%"  (integer)
swift
0.85.formatted(.percent)                                      // "85%"
0.8567.formatted(.percent.precision(.fractionLength(1)))       // "85.7%"
42.formatted(.percent)                                         // "42%"  (整数)

Dates

日期

swift
let now = Date.now

// Components
now.formatted(.dateTime.year().month().day())           // "Apr 22, 2026"
now.formatted(.dateTime.hour().minute())                // "4:30 PM"
now.formatted(.dateTime.weekday(.wide).month(.wide).day()) // "Wednesday, April 22"

// Predefined styles
now.formatted(date: .long, time: .shortened)            // "April 22, 2026 at 4:30 PM"
now.formatted(date: .abbreviated, time: .omitted)       // "Apr 22, 2026"

// ISO 8601
now.formatted(.iso8601)                                 // "2026-04-22T16:30:00Z"

// Relative
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: .now)!
yesterday.formatted(.relative(presentation: .named))    // "yesterday"
yesterday.formatted(.relative(presentation: .numeric))  // "1 day ago"

// Interval
(date1..<date2).formatted(.interval.month().day().hour().minute())

// Components (countdown-style)
(date1..<date2).formatted(.components(style: .wide, fields: [.day, .hour]))
// "2 days, 5 hours"
swift
let now = Date.now

// 组件设置
now.formatted(.dateTime.year().month().day())           // "Apr 22, 2026"
now.formatted(.dateTime.hour().minute())                // "4:30 PM"
now.formatted(.dateTime.weekday(.wide).month(.wide).day()) // "Wednesday, April 22"

// 预定义样式
now.formatted(date: .long, time: .shortened)            // "April 22, 2026 at 4:30 PM"
now.formatted(date: .abbreviated, time: .omitted)       // "Apr 22, 2026"

// ISO 8601 格式
now.formatted(.iso8601)                                 // "2026-04-22T16:30:00Z"

// 相对日期
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: .now)!
yesterday.formatted(.relative(presentation: .named))    // "yesterday"
yesterday.formatted(.relative(presentation: .numeric))  // "1 day ago"

// 时间间隔
(date1..<date2).formatted(.interval.month().day().hour().minute())

// 组件(倒计时样式)
(date1..<date2).formatted(.components(style: .wide, fields: [.day, .hour]))
// "2 days, 5 hours"

Anchored Relative Dates (iOS 18+)

锚定相对日期(iOS 18+)

Date.AnchoredRelativeFormatStyle
formats relative to a fixed anchor date rather than the current moment.
Date.AnchoredRelativeFormatStyle
相对于固定的锚定日期进行格式化,而非当前时刻。

Durations

时长

Duration
(iOS 16+) has two format styles:
Duration
(iOS 16+)拥有两种格式样式:

TimeFormatStyle — compact separator-based

TimeFormatStyle — 紧凑分隔符样式

swift
let d = Duration.seconds(3661)

d.formatted(.time(pattern: .hourMinuteSecond))       // "1:01:01"
d.formatted(.time(pattern: .hourMinute))             // "1:01"
d.formatted(.time(pattern: .minuteSecond))           // "61:01"

// Fractional seconds
Duration.seconds(3.75).formatted(
    .time(pattern: .minuteSecond(padMinuteToLength: 2, fractionalSecondsLength: 2))
)  // "00:03.75"
swift
let d = Duration.seconds(3661)

d.formatted(.time(pattern: .hourMinuteSecond))       // "1:01:01"
d.formatted(.time(pattern: .hourMinute))             // "1:01"
d.formatted(.time(pattern: .minuteSecond))           // "61:01"

// 小数秒
Duration.seconds(3.75).formatted(
    .time(pattern: .minuteSecond(padMinuteToLength: 2, fractionalSecondsLength: 2))
)  // "00:03.75"

UnitsFormatStyle — labeled units

UnitsFormatStyle — 带标签单位样式

swift
Duration.seconds(3661).formatted(
    .units(allowed: [.hours, .minutes, .seconds], width: .abbreviated)
)  // "1 hr, 1 min, 1 sec"

Duration.seconds(90).formatted(
    .units(allowed: [.minutes, .seconds], width: .wide)
)  // "1 minute, 30 seconds"

Duration.seconds(90).formatted(
    .units(allowed: [.minutes, .seconds], width: .narrow)
)  // "1m 30s"

// Limit unit count
Duration.seconds(3661).formatted(
    .units(allowed: [.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 2)
)  // "1 hr, 1 min"
swift
Duration.seconds(3661).formatted(
    .units(allowed: [.hours, .minutes, .seconds], width: .abbreviated)
)  // "1 hr, 1 min, 1 sec"

Duration.seconds(90).formatted(
    .units(allowed: [.minutes, .seconds], width: .wide)
)  // "1 minute, 30 seconds"

Duration.seconds(90).formatted(
    .units(allowed: [.minutes, .seconds], width: .narrow)
)  // "1m 30s"

// 限制单位数量
Duration.seconds(3661).formatted(
    .units(allowed: [.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 2)
)  // "1 hr, 1 min"

Measurements

度量值

swift
let temp = Measurement(value: 72, unit: UnitTemperature.fahrenheit)
temp.formatted(.measurement(width: .wide))        // "72 degrees Fahrenheit"
temp.formatted(.measurement(width: .abbreviated))  // "72°F"
temp.formatted(.measurement(width: .narrow))       // "72°"

let dist = Measurement(value: 5, unit: UnitLength.kilometers)
dist.formatted(.measurement(width: .abbreviated, usage: .road))  // "3.1 mi" (en_US)
swift
let temp = Measurement(value: 72, unit: UnitTemperature.fahrenheit)
temp.formatted(.measurement(width: .wide))        // "72 degrees Fahrenheit"
temp.formatted(.measurement(width: .abbreviated))  // "72°F"
temp.formatted(.measurement(width: .narrow))       // "72°"

let dist = Measurement(value: 5, unit: UnitLength.kilometers)
dist.formatted(.measurement(width: .abbreviated, usage: .road))  // "3.1 mi" (en_US)

Person Names

人名

swift
var name = PersonNameComponents()
name.givenName = "Thomas"
name.familyName = "Clark"
name.middleName = "Louis"
name.namePrefix = "Dr."
name.nickname = "Tom"
name.nameSuffix = "Esq."

name.formatted(.name(style: .long))        // "Dr. Thomas Louis Clark Esq."
name.formatted(.name(style: .medium))      // "Thomas Clark"
name.formatted(.name(style: .short))       // "Tom"
name.formatted(.name(style: .abbreviated)) // "TC"
Style resolution follows priority: script → user preferences → locale → developer setting.
swift
var name = PersonNameComponents()
name.givenName = "Thomas"
name.familyName = "Clark"
name.middleName = "Louis"
name.namePrefix = "Dr."
name.nickname = "Tom"
name.nameSuffix = "Esq."

name.formatted(.name(style: .long))        // "Dr. Thomas Louis Clark Esq."
name.formatted(.name(style: .medium))      // "Thomas Clark"
name.formatted(.name(style: .short))       // "Tom"
name.formatted(.name(style: .abbreviated)) // "TC"
样式解析遵循优先级:脚本 → 用户偏好 → 区域设置 → 开发者设置。

Lists

列表

swift
["Alice", "Bob", "Charlie"].formatted(.list(type: .and))
// "Alice, Bob, and Charlie"

["Alice", "Bob", "Charlie"].formatted(.list(type: .or))
// "Alice, Bob, or Charlie"

// With member formatting
[1, 2, 3].formatted(.list(memberStyle: .number, type: .and))
// "1, 2, and 3"

// Narrow width
["A", "B", "C"].formatted(.list(type: .and, width: .narrow))
// "A, B, C"
swift
["Alice", "Bob", "Charlie"].formatted(.list(type: .and))
// "Alice, Bob, and Charlie"

["Alice", "Bob", "Charlie"].formatted(.list(type: .or))
// "Alice, Bob, or Charlie"

// 成员格式化
[1, 2, 3].formatted(.list(memberStyle: .number, type: .and))
// "1, 2, and 3"

// 窄宽度
["A", "B", "C"].formatted(.list(type: .and, width: .narrow))
// "A, B, C"

Byte Counts

字节数

swift
Int64(1_048_576).formatted(.byteCount(style: .memory))   // "1 MB"
Int64(1_048_576).formatted(.byteCount(style: .file))      // "1 MB"
Int64(1_048_576).formatted(.byteCount(style: .binary))    // "1 MiB"
swift
Int64(1_048_576).formatted(.byteCount(style: .memory))   // "1 MB"
Int64(1_048_576).formatted(.byteCount(style: .file))      // "1 MB"
Int64(1_048_576).formatted(.byteCount(style: .binary))    // "1 MiB"

URLs

URL

swift
let url = URL(string: "https://example.com/path?q=1")!
url.formatted()
// "https://example.com/path?q=1"

url.formatted(.url.scheme(.never).host().path())
// "example.com/path"

url.formatted(.url.scheme(.always).host(.never).path())
// "https:///path"
swift
let url = URL(string: "https://example.com/path?q=1")!
url.formatted()
// "https://example.com/path?q=1"

url.formatted(.url.scheme(.never).host().path())
// "example.com/path"

url.formatted(.url.scheme(.always).host(.never).path())
// "https:///path"

SwiftUI Integration

SwiftUI 集成

Text
accepts a
format:
parameter, keeping formatting out of the view model.
swift
// Inline format style
Text(price, format: .currency(code: "USD"))
Text(date, format: .dateTime.month().day().year())
Text(duration, format: .units(allowed: [.minutes, .seconds]))

// Timer-style (live updating)
Text(.now, style: .timer)
Text(.now, style: .relative)
Text(timerInterval: start...end)
Prefer
Text(_:format:)
over string interpolation
— it allows SwiftUI to re-render only the formatted value and supports accessibility scaling.
Text
接受
format:
参数,将格式化逻辑从视图模型中分离。
swift
// 内联格式样式
Text(price, format: .currency(code: "USD"))
Text(date, format: .dateTime.month().day().year())
Text(duration, format: .units(allowed: [.minutes, .seconds]))

// 计时器样式(实时更新)
Text(.now, style: .timer)
Text(.now, style: .relative)
Text(timerInterval: start...end)
优先使用
Text(_:format:)
而非字符串插值
——它允许SwiftUI仅重新渲染格式化后的值,并支持无障碍缩放。

Custom FormatStyle

自定义FormatStyle

Conform to
FormatStyle
for domain-specific formatting. Conform to
ParseableFormatStyle
if you also need parsing.
swift
struct AbbreviatedCountStyle: FormatStyle {
    func format(_ value: Int) -> String {
        switch value {
        case ..<1_000:
            return "\(value)"
        case 1_000..<1_000_000:
            return String(format: "%.1fK", Double(value) / 1_000)
        default:
            return String(format: "%.1fM", Double(value) / 1_000_000)
        }
    }
}

extension FormatStyle where Self == AbbreviatedCountStyle {
    static var abbreviatedCount: AbbreviatedCountStyle { .init() }
}

// Usage
let followers = 12_500
Text(followers, format: .abbreviatedCount)  // "12.5K"
遵循
FormatStyle
协议实现领域特定的格式化。如果还需要解析功能,需遵循
ParseableFormatStyle
swift
struct AbbreviatedCountStyle: FormatStyle {
    func format(_ value: Int) -> String {
        switch value {
        case ..<1_000:
            return "\(value)"
        case 1_000..<1_000_000:
            return String(format: "%.1fK", Double(value) / 1_000)
        default:
            return String(format: "%.1fM", Double(value) / 1_000_000)
        }
    }
}

extension FormatStyle where Self == AbbreviatedCountStyle {
    static var abbreviatedCount: AbbreviatedCountStyle { .init() }
}

// 使用示例
let followers = 12_500
Text(followers, format: .abbreviatedCount)  // "12.5K"

Common Mistakes

常见错误

MistakeFix
Using legacy
NumberFormatter
/
DateFormatter
in new code
Use
FormatStyle
(iOS 15+). Foundation caches format style instances automatically.
String interpolation for formatted numbers in
Text
Use
Text(value, format:)
for locale correctness and accessibility
Hardcoding locale in format stylesOmit
.locale()
to inherit the user's current locale by default
Using
.time(pattern:)
for labeled duration display
Use
.units(allowed:width:)
for "1 hr, 30 min" style output
Creating
Formatter
instances in
body
or tight loops
FormatStyle instances are value types cached by Foundation; safe to create inline
Formatting
Duration
with
DateComponentsFormatter
Use
Duration.TimeFormatStyle
or
Duration.UnitsFormatStyle
directly
Ignoring
usage:
parameter for measurements
Specify
.road
,
.asProvided
, etc. for locale-aware unit conversion
错误修复方案
在新代码中使用旧版
NumberFormatter
/
DateFormatter
使用
FormatStyle
(iOS 15+)。Foundation会自动缓存格式样式实例。
Text
中使用字符串插值格式化数字
使用
Text(value, format:)
确保区域正确性和无障碍支持
在格式样式中硬编码区域设置默认省略
.locale()
,继承用户当前的区域设置
使用
.time(pattern:)
格式化带标签的时长
使用
.units(allowed:width:)
生成"1 hr, 30 min"样式的输出
body
或循环中创建
Formatter
实例
FormatStyle实例是值类型,由Foundation缓存;可安全地内联创建
使用
DateComponentsFormatter
格式化
Duration
直接使用
Duration.TimeFormatStyle
Duration.UnitsFormatStyle
忽略度量值的
usage:
参数
指定
.road
.asProvided
等,实现区域感知的单位转换

Review Checklist

检查清单

  • FormatStyle
    used instead of legacy
    Formatter
    subclasses for iOS 15+ targets
  • Text(_:format:)
    used instead of pre-formatting strings for SwiftUI text
  • No hardcoded locale unless explicitly needed (e.g., server communication)
  • Duration formatting uses
    Duration.TimeFormatStyle
    or
    Duration.UnitsFormatStyle
  • Currency codes are ISO 4217 strings, not hardcoded symbols
  • Measurement formatting includes
    usage:
    for user-facing display
  • Custom FormatStyle types conform to
    Codable
    +
    Hashable
    for caching
  • 针对iOS 15+的目标,使用
    FormatStyle
    替代旧版
    Formatter
    子类
  • 对于SwiftUI文本,使用
    Text(_:format:)
    而非预格式化字符串
  • 除非明确需要(如服务器通信),否则不硬编码区域设置
  • 时长格式化使用
    Duration.TimeFormatStyle
    Duration.UnitsFormatStyle
  • 货币代码使用ISO 4217字符串,而非硬编码符号
  • 度量值格式化包含
    usage:
    参数以面向用户展示
  • 自定义FormatStyle类型遵循
    Codable
    +
    Hashable
    以支持缓存

References

参考资料