From apple-kit-skills
Formats values for display using the FormatStyle protocol: numbers, currencies, percentages, dates, durations, measurements, person names, byte counts, lists, and URLs. Covers custom conformance and replacing legacy Formatter subclasses.
How this skill is triggered — by the user, by Claude, or both
Slash command
/apple-kit-skills:swift-formatstyleThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Format values for human-readable display using the `FormatStyle` protocol
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.
Docs: FormatStyle
| Type | Style Access | Example |
|---|---|---|
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()) |
// 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"
Docs: IntegerFormatStyle, FloatingPointFormatStyle
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"
0.85.formatted(.percent) // "85%"
0.8567.formatted(.percent.precision(.fractionLength(1))) // "85.7%"
42.formatted(.percent) // "42%" (integer)
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"
Docs: Date.FormatStyle, Date.RelativeFormatStyle, Date.IntervalFormatStyle
Date.AnchoredRelativeFormatStyle formats relative to a fixed anchor date
rather than the current moment.
Docs: Date.AnchoredRelativeFormatStyle
Duration (iOS 16+) has two format styles:
Docs: Duration.TimeFormatStyle, Duration.UnitsFormatStyle
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"
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"
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
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.
Docs: PersonNameComponents.FormatStyle
["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
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
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
Text accepts a format: parameter, keeping formatting out of the view model.
// 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.
Conform to FormatStyle for domain-specific formatting. Conform to
ParseableFormatStyle if you also need parsing.
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"
| Mistake | Fix |
|---|---|
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 styles | Omit .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 |
FormatStyle used instead of legacy Formatter subclasses for iOS 15+ targetsText(_:format:) used instead of pre-formatting strings for SwiftUI textDuration.TimeFormatStyle or Duration.UnitsFormatStyleusage: for user-facing displayCodable + Hashable for cachingnpx claudepluginhub dpearson2699/swift-ios-skills --plugin swiftui-skillsUse this skill for any task involving Thai date formatting, Buddhist Era (พ.ศ.) ↔ Gregorian (ค.ศ.) year conversion, or Arabic ↔ Thai numeral conversion. Trigger whenever the user asks to: convert พ.ศ. to ค.ศ. or vice versa, format a date in Thai government / business / casual style, parse a Thai date string with full or abbreviated month names, or convert digits between 0123456789 and ๐๑๒๓๔๕๖๗๘๙. Also trigger for requests like "แปลง พ.ศ. เป็น ค.ศ.", "จัดรูปแบบวันที่ไทย", "วันที่แบบราชการ", "เปลี่ยนเป็นเลขไทย", or any variation involving Thai calendar dates.
Formats dates into localized strings respecting user language and region settings. Provides fine-grained control over date parts and formatting options like slashes or capitalization.
Localize iOS/macOS apps using String Catalogs, generated symbols, FormatStyle, and RTL-aware layout. Use when adding multi-language support, setting up .xcstrings, handling plurals, or formatting dates/numbers for different locales.