@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,31 +1,37 @@
|
|
|
1
|
-
# FormatStyle
|
|
1
|
+
# FormatStyle, Locale and Layout Direction
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`FormatStyle` needs iOS 15 or later. This skill uses it for locale-aware
|
|
4
|
+
display: numbers, dates, currency, units, names, lists, calendars and
|
|
5
|
+
separators, plus previews and tests across locales. Custom `FormatStyle`
|
|
6
|
+
types, `ParseableFormatStyle`, parsing, `Date.IntervalFormatStyle`,
|
|
7
|
+
`URL.FormatStyle` and reusable formatter API design belong to
|
|
8
|
+
`swift-formatstyle`; here they only get locale risks and testing advice unless
|
|
9
|
+
someone asks for the implementation. Whatever the value, recommend previewing
|
|
10
|
+
it in several locales such as `en_US`, `de_DE`, `ar_SA` and `ja_JP`.
|
|
4
11
|
|
|
5
12
|
## Contents
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
| `
|
|
28
|
-
| `NumberFormatter` | `.formatted(.number...)` or `IntegerFormatStyle` / `FloatingPointFormatStyle` |
|
|
14
|
+
1. [Replacing legacy formatters](#replacing-legacy-formatters)
|
|
15
|
+
2. [Dates](#dates)
|
|
16
|
+
3. [Numbers](#numbers)
|
|
17
|
+
4. [Measurements](#measurements)
|
|
18
|
+
5. [Durations](#durations)
|
|
19
|
+
6. [Person names](#person-names)
|
|
20
|
+
7. [Byte counts](#byte-counts)
|
|
21
|
+
8. [Lists](#lists)
|
|
22
|
+
9. [Custom styles](#custom-styles)
|
|
23
|
+
10. [Pinning a locale](#pinning-a-locale)
|
|
24
|
+
11. [Right-to-left in depth](#right-to-left-in-depth)
|
|
25
|
+
12. [ScaledMetric and Dynamic Type](#scaledmetric-and-dynamic-type)
|
|
26
|
+
13. [Checking layout with Accessibility Inspector](#checking-layout-with-accessibility-inspector)
|
|
27
|
+
14. [Cheat sheet](#cheat-sheet)
|
|
28
|
+
|
|
29
|
+
## Replacing legacy formatters
|
|
30
|
+
|
|
31
|
+
| Old formatter | FormatStyle replacement |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `DateFormatter` | `.formatted(.dateTime...)`, `Date.FormatStyle` |
|
|
34
|
+
| `NumberFormatter` | `.formatted(.number...)`, `IntegerFormatStyle`, `FloatingPointFormatStyle` |
|
|
29
35
|
| `DateComponentsFormatter` | `Duration.formatted(.units(...))` or `.time(pattern:)` |
|
|
30
36
|
| `MeasurementFormatter` | `Measurement.formatted(.measurement(...))` |
|
|
31
37
|
| `DateIntervalFormatter` | `(start..<end).formatted(date:time:)` |
|
|
@@ -33,612 +39,398 @@ Comprehensive reference for locale-aware formatting in iOS 15+ using `FormatStyl
|
|
|
33
39
|
| `ByteCountFormatter` | `.formatted(.byteCount(style:))` |
|
|
34
40
|
| `ListFormatter` | `.formatted(.list(type:))` |
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
Format styles are value types, `Sendable` and composable, and SwiftUI `Text`
|
|
43
|
+
accepts them directly. The old formatters are classes whose locale and calendar
|
|
44
|
+
you had to configure and share by hand.
|
|
37
45
|
|
|
38
|
-
##
|
|
46
|
+
## Dates
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
Presets:
|
|
41
49
|
|
|
42
50
|
```swift
|
|
43
|
-
let
|
|
44
|
-
|
|
45
|
-
// Date only
|
|
46
|
-
date.formatted(date: .numeric, time: .omitted) // "1/15/2026" (US) / "15.01.2026" (DE)
|
|
47
|
-
date.formatted(date: .abbreviated, time: .omitted) // "Jan 15, 2026" (US) / "15. Jan. 2026" (DE)
|
|
48
|
-
date.formatted(date: .long, time: .omitted) // "January 15, 2026" (US) / "15. Januar 2026" (DE)
|
|
49
|
-
date.formatted(date: .complete, time: .omitted) // "Thursday, January 15, 2026" (US)
|
|
50
|
-
|
|
51
|
-
// Time only
|
|
52
|
-
date.formatted(date: .omitted, time: .shortened) // "3:30 PM" (US) / "15:30" (DE)
|
|
53
|
-
date.formatted(date: .omitted, time: .standard) // "3:30:45 PM" (US) / "15:30:45" (DE)
|
|
54
|
-
date.formatted(date: .omitted, time: .complete) // includes time zone
|
|
55
|
-
|
|
56
|
-
// Combined
|
|
57
|
-
date.formatted(date: .long, time: .shortened) // "January 15, 2026 at 3:30 PM"
|
|
58
|
-
date.formatted() // platform default
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Component-based date formatting
|
|
51
|
+
let pickup = Date.now
|
|
62
52
|
|
|
63
|
-
|
|
53
|
+
pickup.formatted(date: .numeric, time: .omitted) // 6/3/2026
|
|
54
|
+
pickup.formatted(date: .abbreviated, time: .omitted) // Jun 3, 2026
|
|
55
|
+
pickup.formatted(date: .long, time: .omitted) // June 3, 2026
|
|
56
|
+
pickup.formatted(date: .complete, time: .omitted) // Wednesday, June 3, 2026
|
|
64
57
|
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// Full date with weekday
|
|
70
|
-
date.formatted(.dateTime.weekday(.wide).month(.wide).day().year())
|
|
71
|
-
// "Thursday, January 15, 2026" (US) / "Donnerstag, 15. Januar 2026" (DE)
|
|
72
|
-
|
|
73
|
-
// Month name styles
|
|
74
|
-
date.formatted(.dateTime.month(.wide)) // "January"
|
|
75
|
-
date.formatted(.dateTime.month(.abbreviated)) // "Jan"
|
|
76
|
-
date.formatted(.dateTime.month(.narrow)) // "J"
|
|
77
|
-
date.formatted(.dateTime.month(.twoDigits)) // "01"
|
|
78
|
-
|
|
79
|
-
// Day styles
|
|
80
|
-
date.formatted(.dateTime.day(.twoDigits)) // "15"
|
|
81
|
-
date.formatted(.dateTime.day(.ordinalOfDayInMonth)) // "3" (third Thursday)
|
|
82
|
-
|
|
83
|
-
// Year
|
|
84
|
-
date.formatted(.dateTime.year(.defaultDigits)) // "2026"
|
|
85
|
-
date.formatted(.dateTime.year(.twoDigits)) // "26"
|
|
86
|
-
|
|
87
|
-
// Hour/minute
|
|
88
|
-
date.formatted(.dateTime.hour().minute()) // "3:30 PM" (US, 12h) / "15:30" (DE, 24h)
|
|
89
|
-
date.formatted(.dateTime.hour(.defaultDigits(amPM: .omitted)).minute()) // "3:30"
|
|
58
|
+
pickup.formatted(date: .omitted, time: .shortened) // 9:40 AM
|
|
59
|
+
pickup.formatted(date: .omitted, time: .standard) // 9:40:12 AM
|
|
60
|
+
pickup.formatted(date: .omitted, time: .complete) // adds the time zone
|
|
61
|
+
pickup.formatted() // platform default
|
|
90
62
|
```
|
|
91
63
|
|
|
92
|
-
|
|
64
|
+
Components: list what you want and the system arranges it for the locale.
|
|
93
65
|
|
|
94
66
|
```swift
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
Text(event.date, style: .timer) // counts up/down live
|
|
104
|
-
Text(event.date, style: .offset) // "+2 hours" / "-3 days"
|
|
67
|
+
pickup.formatted(.dateTime.weekday(.abbreviated).day().month(.wide))
|
|
68
|
+
pickup.formatted(.dateTime.month(.narrow)) // "J"
|
|
69
|
+
pickup.formatted(.dateTime.month(.twoDigits).day(.twoDigits))
|
|
70
|
+
pickup.formatted(.dateTime.day(.ordinalOfDayInMonth)) // first Wednesday -> 1
|
|
71
|
+
pickup.formatted(.dateTime.year(.twoDigits))
|
|
72
|
+
pickup.formatted(.dateTime.year(.defaultDigits))
|
|
73
|
+
pickup.formatted(.dateTime.hour().minute()) // 12h or 24h per locale
|
|
74
|
+
pickup.formatted(.dateTime.hour(.defaultDigits(amPM: .omitted)).minute())
|
|
105
75
|
```
|
|
106
76
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Relative (named style)
|
|
111
|
-
let relative = date.formatted(.relative(presentation: .named))
|
|
112
|
-
// "yesterday", "today", "tomorrow", "last Friday", "in 2 weeks"
|
|
113
|
-
|
|
114
|
-
// Relative (numeric style)
|
|
115
|
-
let relativeNum = date.formatted(.relative(presentation: .numeric))
|
|
116
|
-
// "1 day ago", "in 2 days", "3 weeks ago"
|
|
117
|
-
|
|
118
|
-
// Relative with specific units
|
|
119
|
-
let relativeCustom = date.formatted(.relative(presentation: .named, unitsStyle: .wide))
|
|
120
|
-
```
|
|
77
|
+
`month(_:)` takes `.wide`, `.abbreviated`, `.narrow`, `.twoDigits`. `day(_:)`
|
|
78
|
+
takes `.twoDigits` or `.ordinalOfDayInMonth`, which counts which occurrence of
|
|
79
|
+
that weekday the date is in the month.
|
|
121
80
|
|
|
122
|
-
|
|
81
|
+
In SwiftUI:
|
|
123
82
|
|
|
124
83
|
```swift
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
//
|
|
129
|
-
(
|
|
130
|
-
// "Jan 15 - 20, 2026" (smart about shared month/year)
|
|
131
|
-
|
|
132
|
-
// Duration of interval
|
|
133
|
-
(start..<end).formatted(.components(style: .wide))
|
|
134
|
-
// "5 days"
|
|
84
|
+
Text(pickup, format: .dateTime.day().month())
|
|
85
|
+
Text(pickup...pickup.addingTimeInterval(7_200)) // a range
|
|
86
|
+
Text(pickup, style: .relative) // updates itself
|
|
87
|
+
Text(pickup, style: .timer) // counts live
|
|
88
|
+
Text(pickup, style: .offset)
|
|
135
89
|
```
|
|
136
90
|
|
|
137
|
-
|
|
91
|
+
Relative phrasing:
|
|
138
92
|
|
|
139
93
|
```swift
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
94
|
+
let lastSync = Date.now.addingTimeInterval(-86_400)
|
|
95
|
+
lastSync.formatted(.relative(presentation: .named)) // "yesterday"
|
|
96
|
+
lastSync.formatted(.relative(presentation: .numeric)) // "1 day ago"
|
|
97
|
+
lastSync.formatted(.relative(presentation: .numeric, unitsStyle: .wide))
|
|
143
98
|
```
|
|
144
99
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
### Integer and decimal
|
|
100
|
+
Ranges:
|
|
148
101
|
|
|
149
102
|
```swift
|
|
150
|
-
let
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
let decimal = 3.14159
|
|
157
|
-
decimal.formatted(.number.precision(.fractionLength(2))) // "3.14"
|
|
158
|
-
decimal.formatted(.number.precision(.fractionLength(0...3))) // "3.142"
|
|
159
|
-
|
|
160
|
-
// Notation
|
|
161
|
-
value.formatted(.number.notation(.compactName)) // "1.2M" (US) / "1,2 Mio." (DE)
|
|
162
|
-
value.formatted(.number.notation(.scientific)) // "1.234567E6"
|
|
103
|
+
let checkIn = Date.now
|
|
104
|
+
let checkOut = checkIn.addingTimeInterval(4 * 86_400)
|
|
105
|
+
(checkIn..<checkOut).formatted(date: .abbreviated, time: .omitted)
|
|
106
|
+
// shared month and year are written once: "Jun 3 - 7, 2026"
|
|
107
|
+
(checkIn..<checkOut).formatted(.components(style: .wide)) // "4 days"
|
|
163
108
|
```
|
|
164
109
|
|
|
165
|
-
|
|
110
|
+
ISO 8601 is for the wire, never for people:
|
|
166
111
|
|
|
167
112
|
```swift
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
// Round to 2 fraction digits
|
|
171
|
-
num.formatted(.number.precision(.fractionLength(2)).rounded(rule: .up)) // "3.46"
|
|
172
|
-
num.formatted(.number.precision(.fractionLength(2)).rounded(rule: .down)) // "3.45"
|
|
173
|
-
num.formatted(.number.precision(.fractionLength(2)).rounded(rule: .toNearestOrEven)) // "3.46"
|
|
113
|
+
pickup.formatted(.iso8601.dateSeparator(.dash).timeSeparator(.colon))
|
|
174
114
|
```
|
|
175
115
|
|
|
176
|
-
|
|
116
|
+
## Numbers
|
|
177
117
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
ratio.formatted(.percent) // "86%" (US) / "86 %" (FR)
|
|
182
|
-
ratio.formatted(.percent.precision(.fractionLength(1))) // "85.6%"
|
|
183
|
-
|
|
184
|
-
// Integer percentage
|
|
185
|
-
let score = 92
|
|
186
|
-
score.formatted(.percent) // "9,200%" -- probably not what you want!
|
|
187
|
-
// For integer percentages, divide first:
|
|
188
|
-
(Double(score) / 100).formatted(.percent) // "92%"
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
### Currency
|
|
192
|
-
|
|
193
|
-
Always specify the currency code explicitly. The locale controls formatting (symbol position, decimal separator), but the currency code determines the currency.
|
|
118
|
+
Grouping follows the locale: 2,500,000 in the US, 2.500.000 in Germany,
|
|
119
|
+
2 500 000 in France.
|
|
194
120
|
|
|
195
121
|
```swift
|
|
196
|
-
|
|
122
|
+
2_500_000.formatted() // locale grouping
|
|
123
|
+
2_500_000.formatted(.number.grouping(.never)) // 2500000
|
|
197
124
|
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
price.formatted(.currency(code: "JPY")) // "JPY30" (no decimals for yen)
|
|
125
|
+
0.004218.formatted(.number.precision(.significantDigits(3))) // 0.00422
|
|
126
|
+
72.5.formatted(.number.precision(.fractionLength(2))) // 72.50
|
|
127
|
+
72.5.formatted(.number.precision(.fractionLength(0...3))) // 72.5
|
|
202
128
|
|
|
203
|
-
//
|
|
204
|
-
|
|
129
|
+
4_300_000.formatted(.number.notation(.compactName)) // "4.3M", German "4,3 Mio."
|
|
130
|
+
6_022.0.formatted(.number.notation(.scientific))
|
|
205
131
|
|
|
206
|
-
//
|
|
207
|
-
|
|
132
|
+
18.6.formatted(.number.rounded(rule: .up, increment: 1)) // 19
|
|
133
|
+
18.6.formatted(.number.rounded(rule: .down, increment: 1)) // 18
|
|
134
|
+
18.5.formatted(.number.rounded(rule: .toNearestOrEven, increment: 1)) // 18
|
|
208
135
|
```
|
|
209
136
|
|
|
210
|
-
|
|
137
|
+
Without `increment:` (or a `.precision` of zero fraction digits) the rule only
|
|
138
|
+
applies at the style's default precision, so `18.6` still prints "18.6".
|
|
211
139
|
|
|
212
|
-
|
|
140
|
+
Percent multiplies by 100. The integer `92` with `.percent` prints
|
|
141
|
+
"9,200%". Convert to a fraction first:
|
|
213
142
|
|
|
214
143
|
```swift
|
|
215
|
-
let
|
|
216
|
-
|
|
144
|
+
let batteryLevel = 92
|
|
145
|
+
(Double(batteryLevel) / 100).formatted(.percent) // "92%"
|
|
146
|
+
0.1275.formatted(.percent.precision(.fractionLength(1))) // "12.8%"
|
|
217
147
|
```
|
|
218
148
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
The system auto-converts units based on locale (metric vs imperial) unless you opt out.
|
|
222
|
-
|
|
223
|
-
### Length / distance
|
|
149
|
+
Currency: always pass `code:`. The code picks the currency; the locale only
|
|
150
|
+
decides where the symbol goes and which separators are used.
|
|
224
151
|
|
|
225
152
|
```swift
|
|
226
|
-
let
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
distance.formatted(.measurement(width: .abbreviated)) // "3.1 mi" (US) / "5 km" (DE)
|
|
234
|
-
distance.formatted(.measurement(width: .narrow)) // "3.1mi" (US) / "5km" (DE)
|
|
235
|
-
|
|
236
|
-
// Prevent auto-conversion (keep original unit)
|
|
237
|
-
distance.formatted(.measurement(width: .wide, usage: .asProvided))
|
|
238
|
-
// US: "5 kilometers" (keeps km even in US locale)
|
|
153
|
+
let fare = Decimal(124_950) / 100
|
|
154
|
+
fare.formatted(.currency(code: "USD")) // $1,249.50 in en_US
|
|
155
|
+
fare.formatted(.currency(code: "EUR")) // de_DE: "1.249,50" then the euro sign
|
|
156
|
+
Decimal(3200).formatted(.currency(code: "JPY")) // no decimals for yen
|
|
157
|
+
fare.formatted(.currency(code: "CAD").presentation(.narrow)) // "$" when space is tight
|
|
158
|
+
Text(fare, format: .currency(code: "GBP"))
|
|
239
159
|
```
|
|
240
160
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
```swift
|
|
244
|
-
let weight = Measurement(value: 75, unit: UnitMass.kilograms)
|
|
161
|
+
Money belongs in `Decimal`, not `Double`, so binary rounding never shows up in
|
|
162
|
+
a price.
|
|
245
163
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
weight.formatted(.measurement(width: .abbreviated, usage: .personWeight))
|
|
250
|
-
// Uses locale-appropriate unit for body weight
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### Temperature
|
|
164
|
+
Ordinals: the iOS 26.4 SDK has no ordinal `FormatStyle` (`.number.notation`
|
|
165
|
+
offers only `.automatic`, `.compactName` and `.scientific`), so this is the one
|
|
166
|
+
case that still uses `NumberFormatter`:
|
|
254
167
|
|
|
255
168
|
```swift
|
|
256
|
-
let
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// US: "72 F" (auto-converts!) / FR: "22 C" / DE: "22 C"
|
|
260
|
-
|
|
261
|
-
// Weather-specific (ensures locale-correct unit)
|
|
262
|
-
temp.formatted(.measurement(width: .abbreviated, usage: .weather))
|
|
169
|
+
let ordinals = NumberFormatter()
|
|
170
|
+
ordinals.numberStyle = .ordinal
|
|
171
|
+
ordinals.string(from: 3) // "3rd", German "3.", French "3e"
|
|
263
172
|
```
|
|
264
173
|
|
|
265
|
-
|
|
174
|
+
## Measurements
|
|
266
175
|
|
|
267
|
-
|
|
268
|
-
let speed = Measurement(value: 100, unit: UnitSpeed.kilometersPerHour)
|
|
269
|
-
speed.formatted(.measurement(width: .abbreviated))
|
|
270
|
-
// US: "62.1 mph" / DE: "100 km/h"
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### Volume
|
|
176
|
+
Units convert to the locale's system (metric or imperial) unless you opt out.
|
|
274
177
|
|
|
275
178
|
```swift
|
|
276
|
-
let
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
179
|
+
let hike = Measurement(value: 12.4, unit: UnitLength.kilometers)
|
|
180
|
+
hike.formatted(.measurement(width: .wide)) // "7.7 miles" in en_US
|
|
181
|
+
hike.formatted(.measurement(width: .abbreviated)) // "7.7 mi"
|
|
182
|
+
hike.formatted(.measurement(width: .narrow)) // "7.7mi"
|
|
183
|
+
hike.formatted(.measurement(width: .wide, usage: .asProvided)) // keeps km
|
|
280
184
|
|
|
281
|
-
|
|
185
|
+
let luggage = Measurement(value: 23, unit: UnitMass.kilograms)
|
|
186
|
+
luggage.formatted(.measurement(width: .abbreviated, usage: .personWeight))
|
|
282
187
|
|
|
283
|
-
|
|
188
|
+
let outside = Measurement(value: 28, unit: UnitTemperature.celsius)
|
|
189
|
+
outside.formatted(.measurement(width: .abbreviated, usage: .weather)) // F in en_US
|
|
284
190
|
|
|
285
|
-
|
|
286
|
-
|
|
191
|
+
let windSpeed = Measurement(value: 40, unit: UnitSpeed.kilometersPerHour)
|
|
192
|
+
windSpeed.formatted(.measurement(width: .abbreviated)) // mph in en_US
|
|
287
193
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
dur.formatted(.time(pattern: .minuteSecond)) // "61:01"
|
|
194
|
+
let bottle = Measurement(value: 500, unit: UnitVolume.milliliters)
|
|
195
|
+
bottle.formatted(.measurement(width: .abbreviated, usage: .liquid)) // "1.1 pt" in en_US
|
|
291
196
|
```
|
|
292
197
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
```swift
|
|
296
|
-
dur.formatted(.units(allowed: [.hours, .minutes], width: .wide))
|
|
297
|
-
// "1 hour, 1 minute" (EN) / "1 Stunde, 1 Minute" (DE)
|
|
198
|
+
Usage hints such as `.personWeight`, `.weather` and `.liquid` (iOS 16) choose
|
|
199
|
+
the unit people expect for that kind of value.
|
|
298
200
|
|
|
299
|
-
|
|
300
|
-
// "1 hr, 1 min" (EN) / "1 Std., 1 Min." (DE)
|
|
201
|
+
## Durations
|
|
301
202
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
// "1
|
|
203
|
+
```swift
|
|
204
|
+
let workout = Duration.seconds(3_725)
|
|
205
|
+
workout.formatted(.time(pattern: .hourMinuteSecond)) // "1:02:05"
|
|
206
|
+
workout.formatted(.time(pattern: .hourMinute)) // "1:02"
|
|
207
|
+
workout.formatted(.time(pattern: .minuteSecond)) // "62:05", minutes may pass 59
|
|
208
|
+
|
|
209
|
+
// iOS 16+
|
|
210
|
+
workout.formatted(.units(allowed: [.hours, .minutes], width: .wide)) // "1 hour, 2 minutes"
|
|
211
|
+
workout.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .abbreviated))
|
|
212
|
+
workout.formatted(.units(width: .narrow, maximumUnitCount: 1)) // "1h"
|
|
310
213
|
```
|
|
311
214
|
|
|
312
|
-
|
|
215
|
+
Widths are `.wide`, `.abbreviated` and `.narrow`; `maximumUnitCount:` drops the
|
|
216
|
+
smaller units.
|
|
313
217
|
|
|
314
|
-
|
|
218
|
+
## Person names
|
|
315
219
|
|
|
316
220
|
```swift
|
|
317
|
-
var
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
// In SwiftUI
|
|
329
|
-
Text(name, format: .name(style: .medium))
|
|
221
|
+
var guest = PersonNameComponents()
|
|
222
|
+
guest.namePrefix = "Dr."
|
|
223
|
+
guest.givenName = "Hana"
|
|
224
|
+
guest.familyName = "Sato"
|
|
225
|
+
|
|
226
|
+
guest.formatted(.name(style: .long)) // includes the prefix; order follows locale
|
|
227
|
+
guest.formatted(.name(style: .medium))
|
|
228
|
+
guest.formatted(.name(style: .short)) // "Hana" in en_US
|
|
229
|
+
guest.formatted(.name(style: .abbreviated)) // initials
|
|
230
|
+
Text(guest, format: .name(style: .medium))
|
|
330
231
|
```
|
|
331
232
|
|
|
332
|
-
##
|
|
333
|
-
|
|
334
|
-
Format file sizes with locale-appropriate units.
|
|
233
|
+
## Byte counts
|
|
335
234
|
|
|
336
235
|
```swift
|
|
337
|
-
let
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
// Specific allowed units
|
|
344
|
-
bytes.formatted(.byteCount(style: .file, allowedUnits: [.kb])) // "1,536 kB"
|
|
236
|
+
let backupSize: Int64 = 3_400_000_000
|
|
237
|
+
backupSize.formatted(.byteCount(style: .file))
|
|
238
|
+
backupSize.formatted(.byteCount(style: .memory)) // 1024-based
|
|
239
|
+
backupSize.formatted(.byteCount(style: .binary))
|
|
240
|
+
Int64(5_120).formatted(.byteCount(style: .file, allowedUnits: [.kb]))
|
|
345
241
|
```
|
|
346
242
|
|
|
347
|
-
##
|
|
348
|
-
|
|
349
|
-
Join arrays into grammatically correct lists.
|
|
243
|
+
## Lists
|
|
350
244
|
|
|
351
245
|
```swift
|
|
352
|
-
let
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
// EN: "Apples, Oranges, and Bananas"
|
|
356
|
-
// FR: "Apples, Oranges et Bananas"
|
|
357
|
-
// AR: "Apples وOranges وBananas"
|
|
246
|
+
let toppings = ["olives", "basil", "garlic"]
|
|
247
|
+
toppings.formatted(.list(type: .and)) // "olives, basil, and garlic"; French uses "et"
|
|
248
|
+
toppings.formatted(.list(type: .or))
|
|
358
249
|
|
|
359
|
-
|
|
360
|
-
|
|
250
|
+
let tips: [Decimal] = [5, 10, 15]
|
|
251
|
+
tips.formatted(.list(memberStyle: .currency(code: "USD"), type: .or))
|
|
361
252
|
|
|
362
|
-
//
|
|
363
|
-
let prices = [Decimal(1.99), Decimal(2.49), Decimal(3.99)]
|
|
364
|
-
prices.formatted(.list(memberStyle: .currency(code: "USD"), type: .and))
|
|
365
|
-
// "$1.99, $2.49, and $3.99"
|
|
366
|
-
|
|
367
|
-
// Two items
|
|
368
|
-
["Red", "Blue"].formatted(.list(type: .and))
|
|
369
|
-
// "Red and Blue" (no Oxford comma for two items)
|
|
253
|
+
["olives", "basil"].formatted(.list(type: .and)) // "olives and basil", no serial comma
|
|
370
254
|
```
|
|
371
255
|
|
|
372
|
-
## Custom
|
|
256
|
+
## Custom styles
|
|
373
257
|
|
|
374
|
-
|
|
258
|
+
Designing custom and parseable styles is `swift-formatstyle` territory. The
|
|
259
|
+
shape, for recognition:
|
|
375
260
|
|
|
376
261
|
```swift
|
|
377
|
-
struct
|
|
262
|
+
struct ScoreFormat: FormatStyle {
|
|
378
263
|
func format(_ value: Int) -> String {
|
|
264
|
+
let number = Double(value)
|
|
265
|
+
let style = FloatingPointFormatStyle<Double>.number.precision(.fractionLength(0...1))
|
|
379
266
|
switch value {
|
|
380
|
-
case
|
|
381
|
-
|
|
382
|
-
case 1_000
|
|
383
|
-
|
|
384
|
-
return k.formatted(.number.precision(.fractionLength(0...1))) + "K"
|
|
385
|
-
case 1_000_000..<1_000_000_000:
|
|
386
|
-
let m = Double(value) / 1_000_000.0
|
|
387
|
-
return m.formatted(.number.precision(.fractionLength(0...1))) + "M"
|
|
388
|
-
default:
|
|
389
|
-
let b = Double(value) / 1_000_000_000.0
|
|
390
|
-
return b.formatted(.number.precision(.fractionLength(0...1))) + "B"
|
|
267
|
+
case 1_000_000_000...: return (number / 1_000_000_000).formatted(style) + "B"
|
|
268
|
+
case 1_000_000...: return (number / 1_000_000).formatted(style) + "M"
|
|
269
|
+
case 1_000...: return (number / 1_000).formatted(style) + "K"
|
|
270
|
+
default: return value.formatted()
|
|
391
271
|
}
|
|
392
272
|
}
|
|
393
273
|
}
|
|
394
274
|
|
|
395
|
-
extension FormatStyle where Self ==
|
|
396
|
-
static var
|
|
275
|
+
extension FormatStyle where Self == ScoreFormat {
|
|
276
|
+
static var score: ScoreFormat { ScoreFormat() }
|
|
397
277
|
}
|
|
398
278
|
|
|
399
|
-
//
|
|
400
|
-
|
|
401
|
-
followers.formatted(.abbreviatedCount) // "12.5K"
|
|
402
|
-
|
|
403
|
-
// In SwiftUI
|
|
404
|
-
Text(followers, format: .abbreviatedCount)
|
|
279
|
+
let shown = 48_200.formatted(.score) // "48.2K"
|
|
280
|
+
Text(48_200, format: .score)
|
|
405
281
|
```
|
|
406
282
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
struct AbbreviatedCountStyle: ParseableFormatStyle {
|
|
411
|
-
var parseStrategy: AbbreviatedCountParseStrategy { .init() }
|
|
412
|
-
|
|
413
|
-
func format(_ value: Int) -> String { /* same as above */ }
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
struct AbbreviatedCountParseStrategy: ParseStrategy {
|
|
417
|
-
func parse(_ value: String) throws -> Int {
|
|
418
|
-
let cleaned = value.uppercased().trimmingCharacters(in: .whitespaces)
|
|
419
|
-
if cleaned.hasSuffix("K") {
|
|
420
|
-
guard let num = Double(cleaned.dropLast()) else { throw parseError }
|
|
421
|
-
return Int(num * 1_000)
|
|
422
|
-
}
|
|
423
|
-
// ... handle M, B, plain numbers
|
|
424
|
-
guard let num = Int(cleaned) else { throw parseError }
|
|
425
|
-
return num
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
private var parseError: some Error {
|
|
429
|
-
DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Invalid count"))
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
```
|
|
283
|
+
A parseable variant adds `ParseableFormatStyle` with a `parseStrategy` whose
|
|
284
|
+
`parse(_:) throws` reads the suffix back and throws on malformed input, for
|
|
285
|
+
example `DecodingError.dataCorrupted`. Send any real implementation work there.
|
|
433
286
|
|
|
434
|
-
##
|
|
287
|
+
## Pinning a locale
|
|
435
288
|
|
|
436
|
-
|
|
289
|
+
`.locale(_:)` forces a locale, which is right for serialization or for a
|
|
290
|
+
document that must always look one way, such as a German invoice PDF:
|
|
437
291
|
|
|
438
292
|
```swift
|
|
439
|
-
|
|
440
|
-
let
|
|
441
|
-
|
|
442
|
-
// Force a date format for an API
|
|
443
|
-
let apiDate = date.formatted(.iso8601) // Prefer ISO 8601 for APIs
|
|
444
|
-
|
|
445
|
-
// Force German format for a German-language PDF export
|
|
446
|
-
let deDate = date.formatted(.dateTime.month(.wide).day().year().locale(Locale(identifier: "de_DE")))
|
|
293
|
+
let invoiceDate = Date.now.formatted(.dateTime.day().month().year().locale(Locale(identifier: "de_DE")))
|
|
294
|
+
let payloadDate = Date.now.formatted(.iso8601) // preferred for APIs
|
|
447
295
|
```
|
|
448
296
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
## RTL Layout Deep Dive
|
|
452
|
-
|
|
453
|
-
### How SwiftUI auto-mirrors
|
|
297
|
+
Never pin a locale in user-facing UI.
|
|
454
298
|
|
|
455
|
-
|
|
299
|
+
## Right-to-left in depth
|
|
456
300
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
4. **NavigationStack**: Back button appears on trailing (left) side
|
|
461
|
-
5. **Lists**: Disclosure chevrons point left
|
|
462
|
-
6. **ScrollView**: Horizontal scrolling starts from the right
|
|
463
|
-
7. **Text alignment**: Default alignment follows reading direction
|
|
301
|
+
SwiftUI takes `layoutDirection` from the environment. Under RTL, `.leading` is
|
|
302
|
+
the right edge, horizontal `ScrollView` content starts at the right, list
|
|
303
|
+
chevrons point left and the back button sits on the left.
|
|
464
304
|
|
|
465
|
-
|
|
305
|
+
Flip: `chevron.forward`, `arrow.right` and custom directional assets, using
|
|
306
|
+
`.flipsForRightToLeftLayoutDirection(true)`.
|
|
466
307
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
Image(systemName: "chevron.forward")
|
|
470
|
-
.flipsForRightToLeftLayoutDirection(true)
|
|
471
|
-
|
|
472
|
-
Image(systemName: "arrow.right")
|
|
473
|
-
.flipsForRightToLeftLayoutDirection(true)
|
|
474
|
-
|
|
475
|
-
Image("progress-arrow")
|
|
476
|
-
.flipsForRightToLeftLayoutDirection(true)
|
|
477
|
-
|
|
478
|
-
// These should NOT flip:
|
|
479
|
-
// - Logos and brand marks
|
|
480
|
-
// - Photos and illustrations
|
|
481
|
-
// - Clock faces (clockwise is universal)
|
|
482
|
-
// - Music notation
|
|
483
|
-
// - Checkmarks
|
|
484
|
-
// - Mathematical symbols (+, -, =)
|
|
485
|
-
// - Media playback controls (play triangle always points right)
|
|
486
|
-
|
|
487
|
-
// SF Symbols with .rtl variant auto-flip (e.g., text.alignleft has text.alignright)
|
|
488
|
-
// Check SF Symbols app for RTL variants
|
|
489
|
-
```
|
|
308
|
+
Do not flip: logos and brand marks, photos and illustrations, clock faces,
|
|
309
|
+
music notation, checkmarks, mathematical symbols, media playback controls.
|
|
490
310
|
|
|
491
|
-
|
|
311
|
+
SF Symbols that ship an RTL variant switch on their own; the SF Symbols app
|
|
312
|
+
shows which do.
|
|
492
313
|
|
|
493
314
|
```swift
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
ContentView()
|
|
315
|
+
#Preview("Seat map, Hebrew") {
|
|
316
|
+
SeatMapView()
|
|
497
317
|
.environment(\.layoutDirection, .rightToLeft)
|
|
498
|
-
.environment(\.locale, Locale(identifier: "
|
|
318
|
+
.environment(\.locale, Locale(identifier: "he"))
|
|
499
319
|
}
|
|
500
320
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
.environment(\.layoutDirection, .leftToRight)
|
|
506
|
-
.frame(maxWidth: .infinity)
|
|
507
|
-
Divider()
|
|
508
|
-
ContentView()
|
|
509
|
-
.environment(\.layoutDirection, .rightToLeft)
|
|
510
|
-
.environment(\.locale, Locale(identifier: "ar"))
|
|
511
|
-
.frame(maxWidth: .infinity)
|
|
321
|
+
#Preview("Both directions") {
|
|
322
|
+
HStack {
|
|
323
|
+
FareRow().environment(\.layoutDirection, .leftToRight)
|
|
324
|
+
FareRow().environment(\.layoutDirection, .rightToLeft)
|
|
512
325
|
}
|
|
513
326
|
}
|
|
514
327
|
```
|
|
515
328
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
329
|
+
UIKit views inside a `UIViewRepresentable` read `semanticContentAttribute`:
|
|
330
|
+
`.forceLeftToRight`, `.forceRightToLeft`, or `.unspecified`, the default, which
|
|
331
|
+
follows the system.
|
|
519
332
|
|
|
520
333
|
```swift
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
.unspecified
|
|
334
|
+
struct CardNumberField: UIViewRepresentable {
|
|
335
|
+
func makeUIView(context: Context) -> UITextField {
|
|
336
|
+
let field = UITextField()
|
|
337
|
+
field.semanticContentAttribute = .forceLeftToRight
|
|
338
|
+
return field
|
|
527
339
|
}
|
|
340
|
+
func updateUIView(_ uiView: UITextField, context: Context) {}
|
|
528
341
|
}
|
|
529
342
|
```
|
|
530
343
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
344
|
+
Mixed-direction text follows the Unicode bidirectional algorithm. For edge
|
|
345
|
+
cases like a phone number inside Arabic text, prefix a left-to-right mark
|
|
346
|
+
`\u{200E}`, or set `.environment(\.layoutDirection, .leftToRight)` on that one
|
|
347
|
+
view.
|
|
534
348
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
```
|
|
349
|
+
| Problem | Fix |
|
|
350
|
+
|---|---|
|
|
351
|
+
| `.padding(.left)` | `.padding(.leading)` |
|
|
352
|
+
| `.offset(x:)` to push content one way | Alignment or trailing padding |
|
|
353
|
+
| `.multilineTextAlignment(.left)` | `.multilineTextAlignment(.leading)` |
|
|
354
|
+
| Rounding top-left and top-right corners | Round leading and trailing corners |
|
|
355
|
+
| "Swipe right to archive" | "Swipe toward the leading edge", or rely on system swipe actions |
|
|
543
356
|
|
|
544
|
-
|
|
357
|
+
## ScaledMetric and Dynamic Type
|
|
545
358
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
| Fixed position | `.padding(.left, 16)` | `.padding(.leading, 16)` |
|
|
549
|
-
| Absolute offset | `.offset(x: -20)` for "move left" | Use alignment or `.padding(.trailing)` |
|
|
550
|
-
| Text alignment | `.multilineTextAlignment(.left)` | `.multilineTextAlignment(.leading)` |
|
|
551
|
-
| Corner radius | Only rounding top-left/top-right | Round leading/trailing corners |
|
|
552
|
-
| Swipe gestures | "Swipe right to delete" | "Swipe to leading edge" -- or use system gestures |
|
|
553
|
-
|
|
554
|
-
## `@ScaledMetric` for Dynamic Type
|
|
555
|
-
|
|
556
|
-
Use `@ScaledMetric` to make custom spacing, icon sizes, and padding scale with the user's Dynamic Type setting.
|
|
359
|
+
`@ScaledMetric(relativeTo:)` scales your own numbers, such as spacing, icon
|
|
360
|
+
size and padding, together with the text style you name.
|
|
557
361
|
|
|
558
362
|
```swift
|
|
559
|
-
struct
|
|
560
|
-
@ScaledMetric(relativeTo: .body) private var
|
|
561
|
-
@ScaledMetric(relativeTo: .body) private var
|
|
363
|
+
struct ContactRow: View {
|
|
364
|
+
@ScaledMetric(relativeTo: .body) private var portraitSide: CGFloat = 36
|
|
365
|
+
@ScaledMetric(relativeTo: .body) private var gap: CGFloat = 10
|
|
562
366
|
|
|
563
367
|
var body: some View {
|
|
564
|
-
HStack(spacing:
|
|
565
|
-
|
|
566
|
-
.
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
Text(subtitle).font(.subheadline)
|
|
570
|
-
}
|
|
368
|
+
HStack(spacing: gap) {
|
|
369
|
+
Image(systemName: "person.crop.circle")
|
|
370
|
+
.resizable()
|
|
371
|
+
.frame(width: portraitSide, height: portraitSide)
|
|
372
|
+
Text("Mira Lopez")
|
|
571
373
|
}
|
|
572
374
|
}
|
|
573
375
|
}
|
|
574
376
|
```
|
|
575
377
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
`@ScaledMetric` scales proportionally to a text style. Choose the text style that the metric logically accompanies:
|
|
579
|
-
|
|
580
|
-
| Text style | Base size | Use for |
|
|
581
|
-
|------------|-----------|---------|
|
|
582
|
-
| `.body` | 17pt | General spacing, icons next to body text |
|
|
583
|
-
| `.caption` | 12pt | Small icons, fine spacing |
|
|
584
|
-
| `.title` | 28pt | Large icons, hero spacing |
|
|
585
|
-
| `.largeTitle` | 34pt | Hero images, splash elements |
|
|
378
|
+
Which style to scale against:
|
|
586
379
|
|
|
587
|
-
|
|
380
|
+
| `relativeTo` | Base size | Good for |
|
|
381
|
+
|---|---|---|
|
|
382
|
+
| `.body` | 17 pt | General spacing |
|
|
383
|
+
| `.caption` | 12 pt | Small icons |
|
|
384
|
+
| `.title` | 28 pt | Large icons |
|
|
385
|
+
| `.largeTitle` | 34 pt | Hero elements |
|
|
588
386
|
|
|
589
387
|
```swift
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
ContentView()
|
|
593
|
-
.dynamicTypeSize(.accessibility3)
|
|
388
|
+
#Preview("Accessibility 3") {
|
|
389
|
+
ContactRow().dynamicTypeSize(.accessibility3)
|
|
594
390
|
}
|
|
595
391
|
|
|
596
|
-
|
|
597
|
-
#Preview("Dynamic Type Sizes") {
|
|
392
|
+
#Preview("All sizes") {
|
|
598
393
|
ScrollView {
|
|
599
394
|
ForEach(DynamicTypeSize.allCases, id: \.self) { size in
|
|
600
|
-
|
|
601
|
-
.dynamicTypeSize(size)
|
|
602
|
-
.padding()
|
|
603
|
-
.border(Color.gray)
|
|
395
|
+
ContactRow().dynamicTypeSize(size)
|
|
604
396
|
}
|
|
605
397
|
}
|
|
606
398
|
}
|
|
607
399
|
```
|
|
608
400
|
|
|
609
|
-
##
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
##
|
|
627
|
-
|
|
628
|
-
|
|
|
629
|
-
|
|
630
|
-
| `Date` | `.dateTime.month().day().year()` |
|
|
631
|
-
|
|
|
632
|
-
|
|
|
633
|
-
| `Int` | `.number` |
|
|
634
|
-
| `Int`
|
|
635
|
-
| `Int`
|
|
636
|
-
| `Double` | `.number.precision(.fractionLength(2))` |
|
|
637
|
-
| `Double` | `.percent` |
|
|
638
|
-
| `Decimal` | `.currency(code: "USD")` |
|
|
639
|
-
| `Measurement` | `.measurement(width: .abbreviated)` |
|
|
640
|
-
| `Duration` | `.time(pattern: .hourMinuteSecond)` |
|
|
641
|
-
| `Duration` | `.units(width: .abbreviated)` |
|
|
642
|
-
| `PersonNameComponents` | `.name(style: .medium)` |
|
|
643
|
-
| `Int64`
|
|
644
|
-
| `[String]` | `.list(type: .and)` |
|
|
401
|
+
## Checking layout with Accessibility Inspector
|
|
402
|
+
|
|
403
|
+
Open it from Xcode > Open Developer Tool > Accessibility Inspector.
|
|
404
|
+
|
|
405
|
+
- Audit finds problems, truncated text among them.
|
|
406
|
+
- Inspection shows font sizes and whether an element reacts to Dynamic Type.
|
|
407
|
+
- Settings overrides Dynamic Type, Bold Text and Reduce Motion without touching
|
|
408
|
+
system settings.
|
|
409
|
+
|
|
410
|
+
Routine:
|
|
411
|
+
|
|
412
|
+
1. Run the app in Simulator and point the inspector at it.
|
|
413
|
+
2. Set Dynamic Type to Accessibility XXL.
|
|
414
|
+
3. Visit every screen and look for truncation and overlap.
|
|
415
|
+
4. Switch the scheme's App Language to Arabic.
|
|
416
|
+
5. Visit the screens again for alignment and reading order.
|
|
417
|
+
|
|
418
|
+
## Cheat sheet
|
|
419
|
+
|
|
420
|
+
| Value | Style | en_US sample |
|
|
421
|
+
|---|---|---|
|
|
422
|
+
| `Date` | `.dateTime.month().day().year()` | Jun 3, 2026 |
|
|
423
|
+
| Date range | `(start..<end).formatted(date:time:)` | Jun 3 - 7, 2026 |
|
|
424
|
+
| Relative date | `.relative(presentation: .named)` | yesterday |
|
|
425
|
+
| `Int` | `.number` | 2,500,000 |
|
|
426
|
+
| `Int` | `NumberFormatter` with `numberStyle = .ordinal` | 3rd |
|
|
427
|
+
| `Int` | `.number.notation(.compactName)` | 4.3M |
|
|
428
|
+
| `Double` | `.number.precision(.fractionLength(2))` | 72.50 |
|
|
429
|
+
| `Double` | `.percent` | 92% (for 0.92) |
|
|
430
|
+
| `Decimal` | `.currency(code: "USD")` | $1,249.50 |
|
|
431
|
+
| `Measurement` | `.measurement(width: .abbreviated)` | 7.7 mi |
|
|
432
|
+
| `Duration` | `.time(pattern: .hourMinuteSecond)` | 1:02:05 |
|
|
433
|
+
| `Duration` | `.units(width: .abbreviated)` | 1 hr, 2 min, 5 sec |
|
|
434
|
+
| `PersonNameComponents` | `.name(style: .medium)` | Hana Sato |
|
|
435
|
+
| `Int64` | `.byteCount(style: .file)` | 3.4 GB |
|
|
436
|
+
| `[String]` | `.list(type: .and)` | olives, basil, and garlic |
|