swift-formatstyle
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwift FormatStyle
Swift FormatStyle
Format values for human-readable display using the protocol
and Foundation's concrete format styles. Replaces legacy subclasses
with a type-safe, composable, cacheable API.
FormatStyleFormatterDocs: FormatStyle
使用协议和Foundation的具体格式样式,将值格式化为人类可读的展示形式。它以类型安全、可组合、可缓存的API替代了旧版子类。
FormatStyleFormatter文档:FormatStyle
Contents
目录
Quick Reference
快速参考
| Type | Style Access | Example |
|---|---|---|
| | |
| Currency | | |
| Percent | | |
| | |
| Date range | | |
| Relative date | | |
| | |
| | |
| | |
| | |
| | |
| Byte count | | |
| | |
| 类型 | 样式访问方式 | 示例 |
|---|---|---|
| | |
| 货币 | | |
| 百分比 | | |
| | |
| 日期范围 | | |
| 相对日期 | | |
| | |
| | |
| | |
| | |
| | |
| 字节数 | | |
| | |
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.AnchoredRelativeFormatStyleDate.AnchoredRelativeFormatStyleDurations
时长
DurationDurationTimeFormatStyle — 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)Docs: Measurement.FormatStyle
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"Docs: ListFormatStyle
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"Docs: ByteCountFormatStyle
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"Docs: URL.FormatStyle
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 集成
Textformat: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 over string interpolation — it allows SwiftUI to
re-render only the formatted value and supports accessibility scaling.
Text(_:format:)Textformat: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)优先使用 而非字符串插值——它允许SwiftUI仅重新渲染格式化后的值,并支持无障碍缩放。
Text(_:format:)Custom FormatStyle
自定义FormatStyle
Conform to for domain-specific formatting. Conform to
if you also need parsing.
FormatStyleParseableFormatStyleswift
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"遵循 协议实现领域特定的格式化。如果还需要解析功能,需遵循 。
FormatStyleParseableFormatStyleswift
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
常见错误
| Mistake | Fix |
|---|---|
Using legacy | Use |
String interpolation for formatted numbers in | Use |
| Hardcoding locale in format styles | Omit |
Using | Use |
Creating | FormatStyle instances are value types cached by Foundation; safe to create inline |
Formatting | Use |
Ignoring | Specify |
| 错误 | 修复方案 |
|---|---|
在新代码中使用旧版 | 使用 |
在 | 使用 |
| 在格式样式中硬编码区域设置 | 默认省略 |
使用 | 使用 |
在 | FormatStyle实例是值类型,由Foundation缓存;可安全地内联创建 |
使用 | 直接使用 |
忽略度量值的 | 指定 |
Review Checklist
检查清单
- used instead of legacy
FormatStylesubclasses for iOS 15+ targetsFormatter - used instead of pre-formatting strings for SwiftUI text
Text(_:format:) - No hardcoded locale unless explicitly needed (e.g., server communication)
- Duration formatting uses or
Duration.TimeFormatStyleDuration.UnitsFormatStyle - Currency codes are ISO 4217 strings, not hardcoded symbols
- Measurement formatting includes for user-facing display
usage: - Custom FormatStyle types conform to +
Codablefor cachingHashable
- 针对iOS 15+的目标,使用替代旧版
FormatStyle子类Formatter - 对于SwiftUI文本,使用而非预格式化字符串
Text(_:format:) - 除非明确需要(如服务器通信),否则不硬编码区域设置
- 时长格式化使用或
Duration.TimeFormatStyleDuration.UnitsFormatStyle - 货币代码使用ISO 4217字符串,而非硬编码符号
- 度量值格式化包含参数以面向用户展示
usage: - 自定义FormatStyle类型遵循+
Codable以支持缓存Hashable
References
参考资料
- Apple docs: FormatStyle | Date.FormatStyle | Duration.TimeFormatStyle
- Apple文档:FormatStyle | Date.FormatStyle | Duration.TimeFormatStyle