Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
90
90%
Does it follow best practices?
Impact
—
Average score across 248 eval scenarios
Advisory
Suggest reviewing before use
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.
Locale-aware display is an i18n concern even when the app is not adding new languages. When reviewing user-facing FormatStyle output or SwiftUI Text(_:format:), always include an actionable preview/test step for the exact rendered UI in representative locales such as en_US, de_DE, ar_SA, and ja_JP; check separators, numbering systems, calendars, currency and unit conventions, text direction, and layout-sensitive output. Keep this skill focused on FormatStyle, ParseableFormatStyle, parsing, and reusable formatter API design; route broader localization work such as String Catalogs, bundles, plurals, localized copy, and RTL layout review to ios-localization. Do not use "not adding languages" as the reason to skip ios-localization; locale-sensitive formatting can be a localization review issue without translation work.
Docs: FormatStyle
| Type | Style Access | Example |
|---|---|---|
Int, Double | .number | 42.formatted(.number.precision(.fractionLength(2))) → "42.00" |
Decimal | .number, .percent, .currency(code:) | Decimal(string: "0.1")!.formatted(.percent) -> "10%" |
| 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
Use Decimal.FormatStyle for exact decimal values, especially money-like values
that should not pass through binary floating-point.
let amount = Decimal(string: "12345.67")!
amount.formatted(.number) // "12,345.67" (en_US)
amount.formatted(.number.grouping(.never)) // "12345.67"
Decimal(string: "0.1")!.formatted(.percent) // "10%"
amount.formatted(.currency(code: "USD")) // "$12,345.67"
// Parsing with the same style
let price = try? Decimal("$3,500.63", format: .currency(code: "USD"))Docs: Decimal.FormatStyle
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"
// Treat relative strings as standalone text; embedding them inside another
// sentence can be grammatically wrong in some locales. Recommend previewing or
// testing the exact screen in representative locales before approving copy.
Text(yesterday, format: .relative(presentation: .named))
// 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. It requires iOS 18+.
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
URL.FormatStyle requires iOS 16+. The default style includes scheme, host,
and path. Treat port, query, and fragment as opt-in display components; add
.port(.always), .query(.always), or .fragment(.always) only when those
components should be visible.
let url = URL(string: "https://www.example.com:8080/path?q=1#section")!
url.formatted()
// "https://www.example.com/path"
url.formatted(.url.scheme(.never).host().path())
// "www.example.com/path"
url.formatted(.url.scheme(.never).host().path().query(.always))
// "www.example.com/path?q=1"
url.formatted(.url.scheme(.never).host().path().fragment(.always))
// "www.example.com/path#section"When auditing URL component choices, recommend previewing or testing the exact rendered URL text in representative locales, especially when choosing whether to show or hide scheme, path, query, port, or fragment.
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.
For every SwiftUI formatted Text review, include a representative-locale
preview, UI test, or snapshot test recommendation for the exact screen.
Conform to FormatStyle for domain-specific formatting. Conform to
ParseableFormatStyle if you also need parsing. FormatStyle refines
Decodable, Encodable, and Hashable, and Foundation caches identical
customized style instances, so reusable value-style formatters are cheap.
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"For parseable custom styles, pair formatting with a parse strategy and use the
same conventions in both directions. Prefer this only when users edit or import
the formatted value; display-only styles should stay as FormatStyle.
Docs: ParseableFormatStyle
| 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 |
Assuming URL.formatted() preserves query strings, ports, or fragments | Default URL formatting includes scheme, host, and path only; opt in with .query(.always), .port(.always), or .fragment(.always) |
| Embedding relative date output inside larger sentences | Use Date.RelativeFormatStyle output as standalone text; localized grammar may not fit interpolation |
| Forgetting availability checks | URL.FormatStyle and Duration format styles require iOS 16+; Date.AnchoredRelativeFormatStyle requires iOS 18+ |
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 |
| Using binary floating-point for exact decimal display/parsing | Use Decimal.FormatStyle and matching parse strategies for exact decimal values |
FormatStyle used instead of legacy Formatter subclasses for iOS 15+ targetsURL.FormatStyle and Duration styles gated to iOS 16+; anchored relative dates gated to iOS 18+Text(_:format:) used instead of pre-formatting strings for SwiftUI textText includes an explicit representative-locale preview/test recommendationDecimal.FormatStyle when exact decimal formatting or parsing mattersDuration.TimeFormatStyle or Duration.UnitsFormatStyleusage: for user-facing displayCodable + Hashable for caching.tessl-plugin
skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
references
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references