@devo-bmad-custom/agent-orchestration 1.0.2 → 1.0.3
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/lib/installer.js +33 -0
- package/package.json +1 -1
- package/src/.agents/skills/audit-website/README.md +20 -20
- package/src/.agents/skills/audit-website/SKILL.md +470 -470
- package/src/.agents/skills/audit-website/agents/openai.yaml +6 -6
- package/src/.agents/skills/audit-website/assets/icon-small.svg +41 -41
- package/src/.agents/skills/audit-website/references/OUTPUT-FORMAT.md +250 -250
- package/src/.agents/skills/clean-code-standards/SKILL.md +104 -104
- package/src/.agents/skills/excalidraw-dark-standard/SKILL.md +281 -281
- package/src/.agents/skills/frontend-responsive-design-standards/SKILL.md +434 -434
- package/src/.agents/skills/java-fundamentals/SKILL.md +116 -116
- package/src/.agents/skills/java-performance/SKILL.md +119 -119
- package/src/.agents/skills/next-best-practices/SKILL.md +153 -153
- package/src/.agents/skills/next-best-practices/async-patterns.md +87 -87
- package/src/.agents/skills/next-best-practices/bundling.md +180 -180
- package/src/.agents/skills/next-best-practices/data-patterns.md +297 -297
- package/src/.agents/skills/next-best-practices/debug-tricks.md +105 -105
- package/src/.agents/skills/next-best-practices/directives.md +73 -73
- package/src/.agents/skills/next-best-practices/error-handling.md +227 -227
- package/src/.agents/skills/next-best-practices/file-conventions.md +140 -140
- package/src/.agents/skills/next-best-practices/font.md +245 -245
- package/src/.agents/skills/next-best-practices/functions.md +108 -108
- package/src/.agents/skills/next-best-practices/hydration-error.md +91 -91
- package/src/.agents/skills/next-best-practices/image.md +173 -173
- package/src/.agents/skills/next-best-practices/metadata.md +301 -301
- package/src/.agents/skills/next-best-practices/parallel-routes.md +287 -287
- package/src/.agents/skills/next-best-practices/route-handlers.md +146 -146
- package/src/.agents/skills/next-best-practices/rsc-boundaries.md +159 -159
- package/src/.agents/skills/next-best-practices/runtime-selection.md +39 -39
- package/src/.agents/skills/next-best-practices/scripts.md +141 -141
- package/src/.agents/skills/next-best-practices/self-hosting.md +371 -371
- package/src/.agents/skills/next-best-practices/suspense-boundaries.md +67 -67
- package/src/.agents/skills/nextjs-app-router-patterns/SKILL.md +537 -537
- package/src/.agents/skills/postgresql-optimization/SKILL.md +404 -404
- package/src/.agents/skills/python-backend/SKILL.md +153 -153
- package/src/.agents/skills/python-fundamentals/SKILL.md +234 -234
- package/src/.agents/skills/python-performance/SKILL.md +404 -404
- package/src/.agents/skills/react-expert/SKILL.md +335 -335
- package/src/.agents/skills/redis-best-practices/SKILL.md +438 -438
- package/src/.agents/skills/security-best-practices/SKILL.md +288 -288
- package/src/.agents/skills/security-review/LICENSE +22 -22
- package/src/.agents/skills/security-review/SKILL.md +312 -312
- package/src/.agents/skills/security-review/infrastructure/docker.md +432 -432
- package/src/.agents/skills/security-review/languages/javascript.md +388 -388
- package/src/.agents/skills/security-review/languages/python.md +363 -363
- package/src/.agents/skills/security-review/references/api-security.md +519 -519
- package/src/.agents/skills/security-review/references/authentication.md +353 -353
- package/src/.agents/skills/security-review/references/authorization.md +372 -372
- package/src/.agents/skills/security-review/references/business-logic.md +443 -443
- package/src/.agents/skills/security-review/references/cryptography.md +329 -329
- package/src/.agents/skills/security-review/references/csrf.md +398 -398
- package/src/.agents/skills/security-review/references/data-protection.md +378 -378
- package/src/.agents/skills/security-review/references/deserialization.md +410 -410
- package/src/.agents/skills/security-review/references/error-handling.md +436 -436
- package/src/.agents/skills/security-review/references/file-security.md +457 -457
- package/src/.agents/skills/security-review/references/injection.md +259 -259
- package/src/.agents/skills/security-review/references/logging.md +433 -433
- package/src/.agents/skills/security-review/references/misconfiguration.md +435 -435
- package/src/.agents/skills/security-review/references/modern-threats.md +475 -475
- package/src/.agents/skills/security-review/references/ssrf.md +415 -415
- package/src/.agents/skills/security-review/references/supply-chain.md +405 -405
- package/src/.agents/skills/security-review/references/xss.md +336 -336
- package/src/.agents/skills/subagent-driven-development/SKILL.md +275 -275
- package/src/.agents/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -26
- package/src/.agents/skills/subagent-driven-development/implementer-prompt.md +113 -113
- package/src/.agents/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -61
- package/src/.agents/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/src/.agents/skills/systematic-debugging/SKILL.md +296 -296
- package/src/.agents/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/src/.agents/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/src/.agents/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/src/.agents/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/src/.agents/skills/systematic-debugging/test-academic.md +14 -14
- package/src/.agents/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/src/.agents/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/src/.agents/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/src/.agents/skills/typescript-best-practices/SKILL.md +373 -373
- package/src/.agents/skills/ui-ux-pro-custom/SKILL.md +348 -348
- package/src/.agents/skills/ui-ux-pro-custom/data/charts.csv +26 -26
- package/src/.agents/skills/ui-ux-pro-custom/data/colors.csv +97 -97
- package/src/.agents/skills/ui-ux-pro-custom/data/icons.csv +101 -101
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/SKILL.md +106 -106
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/accessibility.md +475 -475
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/animation.md +466 -466
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/composition-locals.md +231 -231
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/deprecated-patterns.md +323 -323
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/lists-scrolling.md +400 -400
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/modifiers.md +331 -331
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/navigation.md +416 -416
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/performance.md +446 -446
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/side-effects.md +516 -516
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/foundation-source.md +13327 -13327
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/material3-source.md +19097 -19097
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/navigation-source.md +2947 -2947
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/runtime-source.md +11316 -11316
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/ui-source.md +7896 -7896
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/state-management.md +377 -377
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/styles-experimental.md +470 -470
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/theming-material3.md +349 -349
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/view-composition.md +595 -595
- package/src/.agents/skills/ui-ux-pro-custom/data/landing.csv +31 -31
- package/src/.agents/skills/ui-ux-pro-custom/data/mobile-ui-layout.md +654 -654
- package/src/.agents/skills/ui-ux-pro-custom/data/products.csv +96 -96
- package/src/.agents/skills/ui-ux-pro-custom/data/react-performance.csv +45 -45
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/astro.csv +54 -54
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/flutter.csv +53 -53
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/html-tailwind.csv +56 -56
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/jetpack-compose.csv +53 -53
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nextjs.csv +53 -53
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nuxt-ui.csv +51 -51
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nuxtjs.csv +59 -59
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/react-native.csv +56 -56
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/react.csv +54 -54
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/shadcn.csv +61 -61
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/svelte.csv +54 -54
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/swiftui.csv +51 -51
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/vue.csv +50 -50
- package/src/.agents/skills/ui-ux-pro-custom/data/styles.csv +68 -68
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/alarmkit/SKILL.md +438 -438
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/alarmkit/references/alarmkit-patterns.md +584 -584
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-clips/SKILL.md +436 -436
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-intents/SKILL.md +489 -489
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-intents/references/appintents-advanced.md +1076 -1076
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/SKILL.md +340 -340
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/references/privacy-manifest.md +90 -90
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/references/review-checklists.md +106 -106
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/coreml-conversion.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/coreml-optimization.md +344 -344
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/foundation-models.md +508 -508
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/mlx-swift.md +285 -285
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/authentication/SKILL.md +496 -496
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/authentication/references/keychain-biometric.md +211 -211
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/background-processing/SKILL.md +499 -499
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/background-processing/references/background-task-patterns.md +390 -390
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/callkit-voip/SKILL.md +461 -461
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/callkit-voip/references/callkit-patterns.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/cloudkit-sync/SKILL.md +492 -492
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/cloudkit-sync/references/cloudkit-patterns.md +461 -461
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/codable-patterns/SKILL.md +467 -467
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/contacts-framework/SKILL.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/contacts-framework/references/contacts-patterns.md +409 -409
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-bluetooth/SKILL.md +491 -491
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-bluetooth/references/ble-patterns.md +435 -435
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-motion/SKILL.md +388 -388
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-motion/references/motion-patterns.md +405 -405
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-nfc/SKILL.md +495 -495
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-nfc/references/nfc-patterns.md +420 -420
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/coreml/SKILL.md +459 -459
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/coreml/references/coreml-swift-integration.md +765 -765
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/SKILL.md +422 -422
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/references/instruments-guide.md +387 -387
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/references/lldb-patterns.md +298 -298
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/device-integrity/SKILL.md +477 -477
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/energykit/SKILL.md +460 -460
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/energykit/references/energykit-patterns.md +541 -541
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/eventkit-calendar/SKILL.md +483 -483
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/eventkit-calendar/references/eventkit-patterns.md +326 -326
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/healthkit/SKILL.md +498 -498
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/healthkit/references/healthkit-patterns.md +602 -602
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/homekit-matter/SKILL.md +496 -496
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/homekit-matter/references/matter-commissioning.md +455 -455
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-accessibility/SKILL.md +301 -301
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-accessibility/references/a11y-patterns.md +140 -140
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/SKILL.md +418 -418
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/references/formatstyle-locale.md +627 -627
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/references/string-catalogs.md +462 -462
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/SKILL.md +441 -441
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/background-websocket.md +862 -862
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/lightweight-clients.md +93 -93
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/network-framework.md +563 -563
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/urlsession-patterns.md +1116 -1116
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/SKILL.md +496 -496
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/app-review-guidelines.md +174 -174
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/cryptokit-advanced.md +296 -296
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/file-storage-patterns.md +354 -354
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/privacy-manifest.md +117 -117
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/live-activities/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/live-activities/references/live-activity-patterns.md +868 -868
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/SKILL.md +485 -485
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/references/corelocation-patterns.md +730 -730
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/references/mapkit-patterns.md +748 -748
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/metrickit-diagnostics/SKILL.md +479 -479
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/musickit-audio/SKILL.md +395 -395
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/musickit-audio/references/musickit-patterns.md +363 -363
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/natural-language/SKILL.md +412 -412
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/natural-language/references/translation-patterns.md +311 -311
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/passkit-wallet/SKILL.md +398 -398
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/passkit-wallet/references/wallet-passes.md +254 -254
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/SKILL.md +387 -387
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/references/paperkit-integration.md +376 -376
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/references/pencilkit-patterns.md +302 -302
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/permissionkit/SKILL.md +446 -446
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/permissionkit/references/permissionkit-patterns.md +435 -435
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/av-playback.md +701 -701
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/camera-capture.md +774 -774
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/image-loading-caching.md +869 -869
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/photospicker-patterns.md +597 -597
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/references/notification-patterns.md +677 -677
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/references/rich-notifications.md +745 -745
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/realitykit-ar/SKILL.md +479 -479
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/realitykit-ar/references/realitykit-patterns.md +480 -480
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/shareplay-activities/SKILL.md +483 -483
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/shareplay-activities/references/shareplay-patterns.md +544 -544
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/speech-recognition/SKILL.md +485 -485
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/SKILL.md +478 -478
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/references/app-review-guidelines.md +58 -58
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/references/storekit-advanced.md +755 -755
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-charts/SKILL.md +487 -487
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-charts/references/charts-patterns.md +895 -895
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/SKILL.md +408 -408
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/approachable-concurrency.md +80 -80
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/swift-6-2-concurrency.md +233 -233
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/swiftui-concurrency.md +187 -187
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/synchronization-primitives.md +341 -341
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-language/SKILL.md +498 -498
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-language/references/swift-patterns-extended.md +505 -505
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-testing/SKILL.md +467 -467
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-testing/references/testing-patterns.md +504 -504
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/SKILL.md +334 -334
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/core-data-coexistence.md +504 -504
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/swiftdata-advanced.md +975 -975
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/swiftdata-queries.md +675 -675
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/SKILL.md +481 -481
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/references/animation-advanced.md +804 -804
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/references/core-animation-bridge.md +553 -553
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-gestures/SKILL.md +450 -450
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-gestures/references/gesture-patterns.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/SKILL.md +336 -336
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/form.md +97 -97
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/grids.md +69 -69
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/list.md +99 -99
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/scrollview.md +147 -147
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-liquid-glass/SKILL.md +325 -325
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-liquid-glass/references/liquid-glass.md +387 -387
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/SKILL.md +262 -262
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/deeplinks.md +207 -207
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/navigationstack.md +177 -177
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/sheets.md +169 -169
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/tabview.md +178 -178
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/SKILL.md +381 -381
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/architecture-patterns.md +486 -486
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/deprecated-migration.md +1097 -1097
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/design-polish.md +780 -780
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/platform-and-sharing.md +696 -696
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/SKILL.md +491 -491
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +46 -46
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +29 -29
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/understanding-hangs-in-your-app.md +33 -33
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/understanding-improving-swiftui-performance.md +52 -52
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/SKILL.md +428 -428
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/references/hosting-migration.md +534 -534
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/references/representable-recipes.md +1133 -1133
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/tipkit/SKILL.md +494 -494
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/tipkit/references/tipkit-patterns.md +782 -782
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/SKILL.md +475 -475
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/vision-requests.md +736 -736
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/visionkit-scanner.md +738 -738
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/SKILL.md +410 -410
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/references/weatherkit-patterns.md +567 -567
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/SKILL.md +497 -497
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/references/widgetkit-advanced.md +871 -871
- package/src/.agents/skills/ui-ux-pro-custom/data/typography.csv +57 -57
- package/src/.agents/skills/ui-ux-pro-custom/data/ui-reasoning.csv +101 -101
- package/src/.agents/skills/ui-ux-pro-custom/data/ux-guidelines.csv +99 -99
- package/src/.agents/skills/ui-ux-pro-custom/data/web-interface.csv +31 -31
- package/src/.agents/skills/ui-ux-pro-custom/scripts/core.py +253 -253
- package/src/.agents/skills/ui-ux-pro-custom/scripts/design_system.py +1067 -1067
- package/src/.agents/skills/ui-ux-pro-custom/scripts/search.py +114 -114
- package/src/.agents/skills/ux-audit/SKILL.md +150 -150
- package/src/.agents/skills/websocket-engineer/SKILL.md +168 -168
- package/src/.agents/skills/websocket-engineer/references/alternatives.md +391 -391
- package/src/.agents/skills/websocket-engineer/references/patterns.md +400 -400
- package/src/.agents/skills/websocket-engineer/references/protocol.md +195 -195
- package/src/.agents/skills/websocket-engineer/references/scaling.md +333 -333
- package/src/.agents/skills/websocket-engineer/references/security.md +474 -474
- package/src/.agents/skills/writing-skills/SKILL.md +655 -655
- package/src/.agents/skills/writing-skills/anthropic-best-practices.md +1150 -1150
- package/src/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -189
- package/src/.agents/skills/writing-skills/graphviz-conventions.dot +171 -171
- package/src/.agents/skills/writing-skills/persuasion-principles.md +187 -187
- package/src/.agents/skills/writing-skills/render-graphs.js +168 -168
- package/src/.agents/skills/writing-skills/testing-skills-with-subagents.md +384 -384
- package/src/.claude/commands/master-orchestrator.md +15 -0
- package/src/_memory/config.yaml +11 -11
- package/src/_memory/master-orchestrator-sidecar/instructions.md +85 -32
- package/src/_memory/skills/nimbalyst-tracking/SKILL.md +103 -103
- package/src/_memory/skills/writing-skills/SKILL.md +655 -655
- package/src/bmb/agents/agent-builder.md +59 -59
- package/src/bmb/agents/module-builder.md +60 -60
- package/src/bmb/agents/workflow-builder.md +61 -61
- package/src/bmb/config.yaml +12 -12
- package/src/bmb/module-help.csv +13 -13
- package/src/bmb/workflows/agent/data/agent-architecture.md +258 -258
- package/src/bmb/workflows/agent/data/agent-compilation.md +185 -185
- package/src/bmb/workflows/agent/data/agent-menu-patterns.md +189 -189
- package/src/bmb/workflows/agent/data/agent-metadata.md +133 -133
- package/src/bmb/workflows/agent/data/agent-validation.md +111 -111
- package/src/bmb/workflows/agent/data/brainstorm-context.md +96 -96
- package/src/bmb/workflows/agent/data/communication-presets.csv +61 -61
- package/src/bmb/workflows/agent/data/critical-actions.md +75 -75
- package/src/bmb/workflows/agent/data/persona-properties.md +252 -252
- package/src/bmb/workflows/agent/data/principles-crafting.md +142 -142
- package/src/bmb/workflows/agent/data/reference/module-examples/architect.md +68 -68
- package/src/bmb/workflows/agent/data/reference/with-sidecar/journal-keeper/journal-keeper-sidecar/entries/yy-mm-dd-entry-template.md +16 -16
- package/src/bmb/workflows/agent/data/understanding-agent-types.md +126 -126
- package/src/bmb/workflows/agent/steps-c/step-01-brainstorm.md +129 -129
- package/src/bmb/workflows/agent/steps-c/step-02-discovery.md +170 -170
- package/src/bmb/workflows/agent/steps-c/step-03-sidecar-metadata.md +309 -309
- package/src/bmb/workflows/agent/steps-c/step-04-persona.md +213 -213
- package/src/bmb/workflows/agent/steps-c/step-05-commands-menu.md +179 -179
- package/src/bmb/workflows/agent/steps-c/step-06-activation.md +278 -278
- package/src/bmb/workflows/agent/steps-c/step-07-build-agent.md +316 -316
- package/src/bmb/workflows/agent/steps-c/step-08-celebrate.md +247 -247
- package/src/bmb/workflows/agent/steps-e/e-01-load-existing.md +221 -221
- package/src/bmb/workflows/agent/steps-e/e-02-discover-edits.md +195 -195
- package/src/bmb/workflows/agent/steps-e/e-04-sidecar-metadata.md +126 -126
- package/src/bmb/workflows/agent/steps-e/e-05-persona.md +135 -135
- package/src/bmb/workflows/agent/steps-e/e-06-commands-menu.md +123 -123
- package/src/bmb/workflows/agent/steps-e/e-07-activation.md +124 -124
- package/src/bmb/workflows/agent/steps-e/e-08-edit-agent.md +197 -197
- package/src/bmb/workflows/agent/steps-e/e-09-celebrate.md +155 -155
- package/src/bmb/workflows/agent/steps-v/v-01-load-review.md +137 -137
- package/src/bmb/workflows/agent/steps-v/v-02a-validate-metadata.md +116 -116
- package/src/bmb/workflows/agent/steps-v/v-02b-validate-persona.md +124 -124
- package/src/bmb/workflows/agent/steps-v/v-02c-validate-menu.md +127 -127
- package/src/bmb/workflows/agent/steps-v/v-02d-validate-structure.md +134 -134
- package/src/bmb/workflows/agent/steps-v/v-02e-validate-sidecar.md +134 -134
- package/src/bmb/workflows/agent/steps-v/v-03-summary.md +104 -104
- package/src/bmb/workflows/agent/templates/agent-plan.template.md +5 -5
- package/src/bmb/workflows/agent/templates/agent-template.md +89 -89
- package/src/bmb/workflows/agent/workflow-create-agent.md +72 -72
- package/src/bmb/workflows/agent/workflow-edit-agent.md +75 -75
- package/src/bmb/workflows/agent/workflow-validate-agent.md +73 -73
- package/src/bmb/workflows/module/data/agent-architecture.md +179 -179
- package/src/bmb/workflows/module/data/agent-spec-template.md +79 -79
- package/src/bmb/workflows/module/data/module-standards.md +263 -263
- package/src/bmb/workflows/module/data/module-yaml-conventions.md +392 -392
- package/src/bmb/workflows/module/module-help-generate.md +254 -254
- package/src/bmb/workflows/module/steps-b/step-01-welcome.md +148 -148
- package/src/bmb/workflows/module/steps-b/step-02-spark.md +141 -141
- package/src/bmb/workflows/module/steps-b/step-03-module-type.md +149 -149
- package/src/bmb/workflows/module/steps-b/step-04-vision.md +83 -83
- package/src/bmb/workflows/module/steps-b/step-05-identity.md +97 -97
- package/src/bmb/workflows/module/steps-b/step-06-users.md +86 -86
- package/src/bmb/workflows/module/steps-b/step-07-value.md +76 -76
- package/src/bmb/workflows/module/steps-b/step-08-agents.md +97 -97
- package/src/bmb/workflows/module/steps-b/step-09-workflows.md +83 -83
- package/src/bmb/workflows/module/steps-b/step-10-tools.md +91 -91
- package/src/bmb/workflows/module/steps-b/step-11-scenarios.md +84 -84
- package/src/bmb/workflows/module/steps-b/step-12-creative.md +95 -95
- package/src/bmb/workflows/module/steps-b/step-13-review.md +105 -105
- package/src/bmb/workflows/module/steps-b/step-14-finalize.md +117 -117
- package/src/bmb/workflows/module/steps-c/step-01-load-brief.md +179 -179
- package/src/bmb/workflows/module/steps-c/step-01b-continue.md +82 -82
- package/src/bmb/workflows/module/steps-c/step-02-structure.md +105 -105
- package/src/bmb/workflows/module/steps-c/step-03-config.md +119 -119
- package/src/bmb/workflows/module/steps-c/step-04-agents.md +168 -168
- package/src/bmb/workflows/module/steps-c/step-05-workflows.md +184 -184
- package/src/bmb/workflows/module/steps-c/step-06-docs.md +401 -401
- package/src/bmb/workflows/module/steps-c/step-07-complete.md +152 -152
- package/src/bmb/workflows/module/steps-e/step-01-load-target.md +81 -81
- package/src/bmb/workflows/module/steps-e/step-02-select-edit.md +77 -77
- package/src/bmb/workflows/module/steps-e/step-03-apply-edit.md +77 -77
- package/src/bmb/workflows/module/steps-e/step-04-review.md +80 -80
- package/src/bmb/workflows/module/steps-e/step-05-confirm.md +75 -75
- package/src/bmb/workflows/module/steps-v/step-01-load-target.md +96 -96
- package/src/bmb/workflows/module/steps-v/step-02-file-structure.md +93 -93
- package/src/bmb/workflows/module/steps-v/step-03-module-yaml.md +99 -99
- package/src/bmb/workflows/module/steps-v/step-04-agent-specs.md +152 -152
- package/src/bmb/workflows/module/steps-v/step-05-workflow-specs.md +152 -152
- package/src/bmb/workflows/module/steps-v/step-06-documentation.md +143 -143
- package/src/bmb/workflows/module/steps-v/step-07-installation.md +102 -102
- package/src/bmb/workflows/module/steps-v/step-08-report.md +197 -197
- package/src/bmb/workflows/module/templates/brief-template.md +154 -154
- package/src/bmb/workflows/module/templates/workflow-spec-template.md +96 -96
- package/src/bmb/workflows/module/workflow-create-module-brief.md +71 -71
- package/src/bmb/workflows/module/workflow-create-module.md +86 -86
- package/src/bmb/workflows/module/workflow-edit-module.md +66 -66
- package/src/bmb/workflows/module/workflow-validate-module.md +66 -66
- package/src/bmb/workflows/workflow/data/architecture.md +150 -150
- package/src/bmb/workflows/workflow/data/common-workflow-tools.csv +19 -19
- package/src/bmb/workflows/workflow/data/csv-data-file-standards.md +53 -53
- package/src/bmb/workflows/workflow/data/frontmatter-standards.md +184 -184
- package/src/bmb/workflows/workflow/data/input-discovery-standards.md +191 -191
- package/src/bmb/workflows/workflow/data/intent-vs-prescriptive-spectrum.md +44 -44
- package/src/bmb/workflows/workflow/data/menu-handling-standards.md +133 -133
- package/src/bmb/workflows/workflow/data/output-format-standards.md +135 -135
- package/src/bmb/workflows/workflow/data/step-file-rules.md +235 -235
- package/src/bmb/workflows/workflow/data/step-type-patterns.md +257 -257
- package/src/bmb/workflows/workflow/data/subprocess-optimization-patterns.md +188 -188
- package/src/bmb/workflows/workflow/data/trimodal-workflow-structure.md +164 -164
- package/src/bmb/workflows/workflow/data/workflow-chaining-standards.md +222 -222
- package/src/bmb/workflows/workflow/data/workflow-examples.md +232 -232
- package/src/bmb/workflows/workflow/data/workflow-type-criteria.md +134 -134
- package/src/bmb/workflows/workflow/steps-c/step-00-conversion.md +263 -263
- package/src/bmb/workflows/workflow/steps-c/step-01-discovery.md +194 -194
- package/src/bmb/workflows/workflow/steps-c/step-01b-continuation.md +3 -3
- package/src/bmb/workflows/workflow/steps-c/step-02-classification.md +270 -270
- package/src/bmb/workflows/workflow/steps-c/step-03-requirements.md +283 -283
- package/src/bmb/workflows/workflow/steps-c/step-04-tools.md +282 -282
- package/src/bmb/workflows/workflow/steps-c/step-05-plan-review.md +243 -243
- package/src/bmb/workflows/workflow/steps-c/step-06-design.md +330 -330
- package/src/bmb/workflows/workflow/steps-c/step-07-foundation.md +239 -239
- package/src/bmb/workflows/workflow/steps-c/step-08-build-step-01.md +379 -379
- package/src/bmb/workflows/workflow/steps-c/step-09-build-next-step.md +350 -350
- package/src/bmb/workflows/workflow/steps-c/step-10-confirmation.md +322 -322
- package/src/bmb/workflows/workflow/steps-c/step-11-completion.md +191 -191
- package/src/bmb/workflows/workflow/steps-e/step-e-01-assess-workflow.md +237 -237
- package/src/bmb/workflows/workflow/steps-e/step-e-02-discover-edits.md +251 -251
- package/src/bmb/workflows/workflow/steps-e/step-e-03-fix-validation.md +254 -254
- package/src/bmb/workflows/workflow/steps-e/step-e-04-direct-edit.md +277 -277
- package/src/bmb/workflows/workflow/steps-e/step-e-05-apply-edit.md +154 -154
- package/src/bmb/workflows/workflow/steps-e/step-e-06-validate-after.md +190 -190
- package/src/bmb/workflows/workflow/steps-e/step-e-07-complete.md +206 -206
- package/src/bmb/workflows/workflow/steps-v/step-01-validate-max-mode.md +109 -109
- package/src/bmb/workflows/workflow/steps-v/step-01-validate.md +221 -221
- package/src/bmb/workflows/workflow/steps-v/step-01b-structure.md +152 -152
- package/src/bmb/workflows/workflow/steps-v/step-02-frontmatter-validation.md +199 -199
- package/src/bmb/workflows/workflow/steps-v/step-02b-path-violations.md +265 -265
- package/src/bmb/workflows/workflow/steps-v/step-03-menu-validation.md +164 -164
- package/src/bmb/workflows/workflow/steps-v/step-04-step-type-validation.md +211 -211
- package/src/bmb/workflows/workflow/steps-v/step-05-output-format-validation.md +200 -200
- package/src/bmb/workflows/workflow/steps-v/step-06-validation-design-check.md +195 -195
- package/src/bmb/workflows/workflow/steps-v/step-07-instruction-style-check.md +209 -209
- package/src/bmb/workflows/workflow/steps-v/step-08-collaborative-experience-check.md +199 -199
- package/src/bmb/workflows/workflow/steps-v/step-08b-subprocess-optimization.md +179 -179
- package/src/bmb/workflows/workflow/steps-v/step-09-cohesive-review.md +186 -186
- package/src/bmb/workflows/workflow/steps-v/step-10-report-complete.md +154 -154
- package/src/bmb/workflows/workflow/steps-v/step-11-plan-validation.md +237 -237
- package/src/bmb/workflows/workflow/templates/minimal-output-template.md +11 -11
- package/src/bmb/workflows/workflow/templates/step-01-init-continuable-template.md +241 -241
- package/src/bmb/workflows/workflow/templates/step-1b-template.md +224 -224
- package/src/bmb/workflows/workflow/templates/step-template.md +294 -294
- package/src/bmb/workflows/workflow/templates/workflow-template.md +102 -102
- package/src/bmb/workflows/workflow/workflow-create-workflow.md +79 -79
- package/src/bmb/workflows/workflow/workflow-edit-workflow.md +65 -65
- package/src/bmb/workflows/workflow/workflow-rework-workflow.md +65 -65
- package/src/bmb/workflows/workflow/workflow-validate-max-parallel-workflow.md +66 -66
- package/src/bmb/workflows/workflow/workflow-validate-workflow.md +65 -65
- package/src/bmm/agents/analyst.md +104 -104
- package/src/bmm/agents/dev.md +100 -100
- package/src/bmm/agents/qa.md +100 -90
- package/src/bmm/agents/tech-writer/tech-writer.md +94 -94
- package/src/bmm/module-help.csv +31 -31
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01-init.md +115 -115
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01b-continue.md +107 -107
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +141 -141
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +144 -144
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +147 -147
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +161 -161
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-06-complete.md +99 -99
- package/src/bmm/workflows/1-analysis/create-product-brief/workflow.md +57 -57
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-01-init.md +87 -87
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-02-domain-analysis.md +156 -156
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-03-competitive-landscape.md +165 -165
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-04-regulatory-focus.md +140 -140
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-05-technical-trends.md +152 -152
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-06-research-synthesis.md +345 -345
- package/src/bmm/workflows/1-analysis/research/market-steps/step-01-init.md +92 -92
- package/src/bmm/workflows/1-analysis/research/market-steps/step-02-customer-behavior.md +164 -164
- package/src/bmm/workflows/1-analysis/research/market-steps/step-03-customer-pain-points.md +174 -174
- package/src/bmm/workflows/1-analysis/research/market-steps/step-04-customer-decisions.md +184 -184
- package/src/bmm/workflows/1-analysis/research/market-steps/step-05-competitive-analysis.md +105 -105
- package/src/bmm/workflows/1-analysis/research/market-steps/step-06-research-completion.md +360 -360
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-01-init.md +87 -87
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-02-technical-overview.md +165 -165
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-03-integration-patterns.md +174 -174
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-04-architectural-patterns.md +141 -141
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-05-implementation-research.md +159 -159
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-06-research-synthesis.md +387 -387
- package/src/bmm/workflows/1-analysis/research/workflow-domain-research.md +54 -54
- package/src/bmm/workflows/1-analysis/research/workflow-market-research.md +54 -54
- package/src/bmm/workflows/1-analysis/research/workflow-technical-research.md +54 -54
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-01b-continue.md +100 -100
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02-discovery.md +160 -160
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02b-vision.md +88 -88
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02c-executive-summary.md +99 -99
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-03-success.md +169 -169
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-04-journeys.md +156 -156
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-05-domain.md +136 -136
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-06-innovation.md +176 -176
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-07-project-type.md +184 -184
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-08-scoping.md +174 -174
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-09-functional.md +175 -175
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-10-nonfunctional.md +189 -189
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-11-polish.md +162 -162
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-12-complete.md +79 -79
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01-discovery.md +183 -183
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01b-legacy-conversion.md +149 -149
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-02-review.md +187 -187
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-03-edit.md +192 -192
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-04-complete.md +108 -108
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +166 -166
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02-format-detection.md +131 -131
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02b-parity-check.md +150 -150
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-03-density-validation.md +118 -118
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-04-brief-coverage-validation.md +155 -155
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-05-measurability-validation.md +170 -170
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-06-traceability-validation.md +158 -158
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-07-implementation-leakage-validation.md +147 -147
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-08-domain-compliance-validation.md +182 -182
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-09-project-type-validation.md +202 -202
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +148 -148
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +201 -201
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-12-completeness-validation.md +179 -179
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +164 -164
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-create-prd.md +65 -65
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-edit-prd.md +65 -65
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-validate-prd.md +63 -63
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01b-continue.md +63 -63
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +106 -106
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +111 -111
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +115 -115
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +127 -127
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +167 -167
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +143 -143
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +118 -118
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +154 -154
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +136 -136
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +165 -165
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +135 -135
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +192 -192
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-14-complete.md +101 -101
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +45 -45
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-01-document-discovery.md +185 -185
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-02-prd-analysis.md +129 -129
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-03-epic-coverage-validation.md +130 -130
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-04-ux-alignment.md +93 -93
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-05-epic-quality-review.md +196 -196
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-06-final-assessment.md +129 -129
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md +54 -54
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-01b-continue.md +82 -82
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +106 -106
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +138 -138
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +129 -129
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +166 -166
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +186 -186
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +163 -163
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-08-complete.md +38 -38
- package/src/bmm/workflows/3-solutioning/create-architecture/workflow.md +49 -49
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +124 -124
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +122 -122
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +84 -84
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +58 -58
- package/src/bmm/workflows/4-implementation/code-review/workflow.yaml +43 -43
- package/src/bmm/workflows/4-implementation/correct-course/workflow.yaml +53 -53
- package/src/bmm/workflows/4-implementation/create-story/checklist.md +159 -159
- package/src/bmm/workflows/4-implementation/create-story/template.md +79 -79
- package/src/bmm/workflows/4-implementation/create-story/workflow.yaml +52 -52
- package/src/bmm/workflows/4-implementation/dev-story/workflow.yaml +20 -20
- package/src/bmm/workflows/4-implementation/retrospective/workflow.yaml +52 -52
- package/src/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +52 -52
- package/src/bmm/workflows/4-implementation/sprint-status/workflow.yaml +25 -25
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-01-mode-detection.md +158 -158
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-02-context-gathering.md +122 -122
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-03-execute.md +93 -93
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-04-self-check.md +93 -93
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-05-adversarial-review.md +87 -87
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-06-resolve-findings.md +146 -146
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +50 -50
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-02-investigate.md +152 -152
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-03-generate.md +123 -123
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-04-review.md +201 -201
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +79 -79
- package/src/bmm/workflows/document-project/workflow.yaml +22 -22
- package/src/bmm/workflows/generate-project-context/steps/step-01-discover.md +184 -184
- package/src/bmm/workflows/generate-project-context/steps/step-02-generate.md +322 -322
- package/src/bmm/workflows/generate-project-context/steps/step-03-complete.md +235 -235
- package/src/bmm/workflows/generate-project-context/workflow.md +49 -49
- package/src/bmm/workflows/qa/automate/workflow.yaml +233 -233
- package/src/bmm/workflows/qa-generate-e2e-tests/workflow.yaml +42 -42
- package/src/core/config.yaml +9 -9
- package/src/core/module-help.csv +10 -10
- package/src/core/scripts/generate-loop-report.py +72 -72
- package/src/core/tasks/editorial-review-prose.xml +101 -101
- package/src/core/tasks/editorial-review-structure.xml +207 -207
- package/src/core/tasks/help.md +86 -86
- package/src/core/tasks/index-docs.xml +64 -64
- package/src/core/tasks/review-adversarial-general.xml +66 -66
- package/src/core/tasks/review-adversarial-loop.xml +46 -46
- package/src/core/tasks/review-edge-case-hunter.xml +63 -63
- package/src/core/tasks/review-party-loop.xml +46 -46
- package/src/core/tasks/shard-doc.xml +107 -107
- package/src/core/tasks/workflow.xml +235 -235
- package/src/core/templates/review-loop-report.html +88 -88
- package/src/core/templates/review-loop-report.md +5 -5
- package/src/core/workflows/advanced-elicitation/workflow.xml +117 -117
- package/src/core/workflows/brainstorming/steps/step-01-session-setup.md +212 -212
- package/src/core/workflows/brainstorming/steps/step-01b-continue.md +122 -122
- package/src/core/workflows/brainstorming/steps/step-02a-user-selected.md +225 -225
- package/src/core/workflows/brainstorming/steps/step-02b-ai-recommended.md +237 -237
- package/src/core/workflows/brainstorming/steps/step-02c-random-selection.md +209 -209
- package/src/core/workflows/brainstorming/steps/step-02d-progressive-flow.md +264 -264
- package/src/core/workflows/brainstorming/steps/step-02e-deep-dive.md +68 -68
- package/src/core/workflows/brainstorming/steps/step-03-technique-execution.md +403 -403
- package/src/core/workflows/brainstorming/steps/step-04-idea-organization.md +303 -303
- package/src/core/workflows/brainstorming/workflow.md +60 -60
- package/src/core/workflows/extract-trackers/workflow.md +45 -45
- package/src/core/workflows/party-mode/steps/step-01-agent-loading.md +142 -142
- package/src/core/workflows/party-mode/workflow.md +194 -194
- package/src/docs/dev/tmux/actions_popup.py +291 -291
- package/src/docs/dev/tmux/tmux-setup.md +62 -1
|
@@ -1,627 +1,627 @@
|
|
|
1
|
-
# FormatStyle & Locale-Aware Formatting
|
|
2
|
-
|
|
3
|
-
Comprehensive reference for locale-aware formatting in iOS 15+ using `FormatStyle`. Never hard-code date, number, or measurement formats -- these break in every locale except the one you tested.
|
|
4
|
-
|
|
5
|
-
## Contents
|
|
6
|
-
|
|
7
|
-
- [Date Formatting](#date-formatting)
|
|
8
|
-
- [Number Formatting](#number-formatting)
|
|
9
|
-
- [Measurement Formatting](#measurement-formatting)
|
|
10
|
-
- [Duration Formatting](#duration-formatting)
|
|
11
|
-
- [PersonNameComponents Formatting](#personnamecomponents-formatting)
|
|
12
|
-
- [ByteCountFormatStyle](#bytecountformatstyle)
|
|
13
|
-
- [ListFormatStyle](#listformatstyle)
|
|
14
|
-
- [Custom FormatStyle Implementation](#custom-formatstyle-implementation)
|
|
15
|
-
- [Forcing a Specific Locale](#forcing-a-specific-locale)
|
|
16
|
-
- [RTL Layout Deep Dive](#rtl-layout-deep-dive)
|
|
17
|
-
- [@ScaledMetric for Dynamic Type](#scaledmetric-for-dynamic-type)
|
|
18
|
-
- [Layout Testing with Accessibility Inspector](#layout-testing-with-accessibility-inspector)
|
|
19
|
-
- [Quick Reference Table](#quick-reference-table)
|
|
20
|
-
|
|
21
|
-
## Date Formatting
|
|
22
|
-
|
|
23
|
-
### Preset date and time styles
|
|
24
|
-
|
|
25
|
-
```swift
|
|
26
|
-
let date = Date.now
|
|
27
|
-
|
|
28
|
-
// Date only
|
|
29
|
-
date.formatted(date: .numeric, time: .omitted) // "1/15/2026" (US) / "15.01.2026" (DE)
|
|
30
|
-
date.formatted(date: .abbreviated, time: .omitted) // "Jan 15, 2026" (US) / "15. Jan. 2026" (DE)
|
|
31
|
-
date.formatted(date: .long, time: .omitted) // "January 15, 2026" (US) / "15. Januar 2026" (DE)
|
|
32
|
-
date.formatted(date: .complete, time: .omitted) // "Thursday, January 15, 2026" (US)
|
|
33
|
-
|
|
34
|
-
// Time only
|
|
35
|
-
date.formatted(date: .omitted, time: .shortened) // "3:30 PM" (US) / "15:30" (DE)
|
|
36
|
-
date.formatted(date: .omitted, time: .standard) // "3:30:45 PM" (US) / "15:30:45" (DE)
|
|
37
|
-
date.formatted(date: .omitted, time: .complete) // includes time zone
|
|
38
|
-
|
|
39
|
-
// Combined
|
|
40
|
-
date.formatted(date: .long, time: .shortened) // "January 15, 2026 at 3:30 PM"
|
|
41
|
-
date.formatted() // platform default
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### Component-based date formatting
|
|
45
|
-
|
|
46
|
-
Build custom date formats by composing components. The system reorders components for each locale.
|
|
47
|
-
|
|
48
|
-
```swift
|
|
49
|
-
// Month and day
|
|
50
|
-
date.formatted(.dateTime.month().day()) // "Jan 15" (US) / "15 Jan" (UK)
|
|
51
|
-
|
|
52
|
-
// Full date with weekday
|
|
53
|
-
date.formatted(.dateTime.weekday(.wide).month(.wide).day().year())
|
|
54
|
-
// "Thursday, January 15, 2026" (US) / "Donnerstag, 15. Januar 2026" (DE)
|
|
55
|
-
|
|
56
|
-
// Month name styles
|
|
57
|
-
date.formatted(.dateTime.month(.wide)) // "January"
|
|
58
|
-
date.formatted(.dateTime.month(.abbreviated)) // "Jan"
|
|
59
|
-
date.formatted(.dateTime.month(.narrow)) // "J"
|
|
60
|
-
date.formatted(.dateTime.month(.twoDigits)) // "01"
|
|
61
|
-
|
|
62
|
-
// Day styles
|
|
63
|
-
date.formatted(.dateTime.day(.twoDigits)) // "15"
|
|
64
|
-
date.formatted(.dateTime.day(.ordinalOfDayInMonth)) // "3" (third Thursday)
|
|
65
|
-
|
|
66
|
-
// Year
|
|
67
|
-
date.formatted(.dateTime.year(.defaultDigits)) // "2026"
|
|
68
|
-
date.formatted(.dateTime.year(.twoDigits)) // "26"
|
|
69
|
-
|
|
70
|
-
// Hour/minute
|
|
71
|
-
date.formatted(.dateTime.hour().minute()) // "3:30 PM" (US, 12h) / "15:30" (DE, 24h)
|
|
72
|
-
date.formatted(.dateTime.hour(.defaultDigits(amPM: .omitted)).minute()) // "3:30"
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### SwiftUI date display
|
|
76
|
-
|
|
77
|
-
```swift
|
|
78
|
-
// Automatic format
|
|
79
|
-
Text(event.date, format: .dateTime.month().day().year())
|
|
80
|
-
|
|
81
|
-
// Date range
|
|
82
|
-
Text(event.start...event.end) // "Jan 15 - Jan 20, 2026"
|
|
83
|
-
|
|
84
|
-
// Relative (auto-updates)
|
|
85
|
-
Text(event.date, style: .relative) // "2 hours ago", "in 3 days"
|
|
86
|
-
Text(event.date, style: .timer) // counts up/down live
|
|
87
|
-
Text(event.date, style: .offset) // "+2 hours" / "-3 days"
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Relative date formatting
|
|
91
|
-
|
|
92
|
-
```swift
|
|
93
|
-
// Relative (named style)
|
|
94
|
-
let relative = date.formatted(.relative(presentation: .named))
|
|
95
|
-
// "yesterday", "today", "tomorrow", "last Friday", "in 2 weeks"
|
|
96
|
-
|
|
97
|
-
// Relative (numeric style)
|
|
98
|
-
let relativeNum = date.formatted(.relative(presentation: .numeric))
|
|
99
|
-
// "1 day ago", "in 2 days", "3 weeks ago"
|
|
100
|
-
|
|
101
|
-
// Relative with specific units
|
|
102
|
-
let relativeCustom = date.formatted(.relative(presentation: .named, unitsStyle: .wide))
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Date ranges and intervals
|
|
106
|
-
|
|
107
|
-
```swift
|
|
108
|
-
let start = Date.now
|
|
109
|
-
let end = Calendar.current.date(byAdding: .day, value: 5, to: start)!
|
|
110
|
-
|
|
111
|
-
// Range formatting
|
|
112
|
-
(start..<end).formatted(date: .abbreviated, time: .omitted)
|
|
113
|
-
// "Jan 15 - 20, 2026" (smart about shared month/year)
|
|
114
|
-
|
|
115
|
-
// Duration of interval
|
|
116
|
-
(start..<end).formatted(.components(style: .wide))
|
|
117
|
-
// "5 days"
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### ISO 8601 (for APIs, not for display)
|
|
121
|
-
|
|
122
|
-
```swift
|
|
123
|
-
// For serialization to APIs -- NOT for user-facing display
|
|
124
|
-
date.formatted(.iso8601) // "2026-01-15T15:30:45Z"
|
|
125
|
-
date.formatted(.iso8601.dateSeparator(.dash).timeSeparator(.colon))
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Number Formatting
|
|
129
|
-
|
|
130
|
-
### Integer and decimal
|
|
131
|
-
|
|
132
|
-
```swift
|
|
133
|
-
let value = 1234567
|
|
134
|
-
|
|
135
|
-
value.formatted() // "1,234,567" (US) / "1.234.567" (DE) / "1 234 567" (FR)
|
|
136
|
-
value.formatted(.number.grouping(.never)) // "1234567"
|
|
137
|
-
value.formatted(.number.precision(.significantDigits(3))) // "1,230,000"
|
|
138
|
-
|
|
139
|
-
let decimal = 3.14159
|
|
140
|
-
decimal.formatted(.number.precision(.fractionLength(2))) // "3.14"
|
|
141
|
-
decimal.formatted(.number.precision(.fractionLength(0...3))) // "3.142"
|
|
142
|
-
|
|
143
|
-
// Notation
|
|
144
|
-
value.formatted(.number.notation(.compactName)) // "1.2M" (US) / "1,2 Mio." (DE)
|
|
145
|
-
value.formatted(.number.notation(.scientific)) // "1.234567E6"
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Rounding
|
|
149
|
-
|
|
150
|
-
```swift
|
|
151
|
-
let num = 3.456
|
|
152
|
-
|
|
153
|
-
// Round to 2 fraction digits
|
|
154
|
-
num.formatted(.number.precision(.fractionLength(2)).rounded(rule: .up)) // "3.46"
|
|
155
|
-
num.formatted(.number.precision(.fractionLength(2)).rounded(rule: .down)) // "3.45"
|
|
156
|
-
num.formatted(.number.precision(.fractionLength(2)).rounded(rule: .toNearestOrEven)) // "3.46"
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Percent
|
|
160
|
-
|
|
161
|
-
```swift
|
|
162
|
-
let ratio = 0.856
|
|
163
|
-
|
|
164
|
-
ratio.formatted(.percent) // "86%" (US) / "86 %" (FR)
|
|
165
|
-
ratio.formatted(.percent.precision(.fractionLength(1))) // "85.6%"
|
|
166
|
-
|
|
167
|
-
// Integer percentage
|
|
168
|
-
let score = 92
|
|
169
|
-
score.formatted(.percent) // "9,200%" -- probably not what you want!
|
|
170
|
-
// For integer percentages, divide first:
|
|
171
|
-
(Double(score) / 100).formatted(.percent) // "92%"
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### Currency
|
|
175
|
-
|
|
176
|
-
Always specify the currency code explicitly. The locale controls formatting (symbol position, decimal separator), but the currency code determines the currency.
|
|
177
|
-
|
|
178
|
-
```swift
|
|
179
|
-
let price = Decimal(29.99)
|
|
180
|
-
|
|
181
|
-
// Explicit currency code (recommended)
|
|
182
|
-
price.formatted(.currency(code: "USD")) // "$29.99" (US) / "29,99 $US" (FR) / "US$29.99" (AU)
|
|
183
|
-
price.formatted(.currency(code: "EUR")) // "EUR29.99" (US) / "29,99 EUR" (DE) / "29,99 EUR" (FR)
|
|
184
|
-
price.formatted(.currency(code: "JPY")) // "JPY30" (no decimals for yen)
|
|
185
|
-
|
|
186
|
-
// Narrow symbol (when space is limited)
|
|
187
|
-
price.formatted(.currency(code: "USD").presentation(.narrow)) // "$29.99" even in non-US locales
|
|
188
|
-
|
|
189
|
-
// In SwiftUI
|
|
190
|
-
Text(price, format: .currency(code: order.currencyCode))
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
**Important:** Use `Decimal` (not `Double`) for monetary values to avoid floating-point precision errors.
|
|
194
|
-
|
|
195
|
-
### Ordinal numbers
|
|
196
|
-
|
|
197
|
-
```swift
|
|
198
|
-
let position = 3
|
|
199
|
-
position.formatted(.number.notation(.ordinal)) // "3rd" (EN) / "3." (DE) / "3e" (FR)
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
## Measurement Formatting
|
|
203
|
-
|
|
204
|
-
The system auto-converts units based on locale (metric vs imperial) unless you opt out.
|
|
205
|
-
|
|
206
|
-
### Length / distance
|
|
207
|
-
|
|
208
|
-
```swift
|
|
209
|
-
let distance = Measurement(value: 5, unit: UnitLength.kilometers)
|
|
210
|
-
|
|
211
|
-
distance.formatted(.measurement(width: .wide))
|
|
212
|
-
// US: "3.1 miles" (auto-converts to imperial!)
|
|
213
|
-
// DE: "5 Kilometer"
|
|
214
|
-
// JP: "5 km" (with .abbreviated)
|
|
215
|
-
|
|
216
|
-
distance.formatted(.measurement(width: .abbreviated)) // "3.1 mi" (US) / "5 km" (DE)
|
|
217
|
-
distance.formatted(.measurement(width: .narrow)) // "3.1mi" (US) / "5km" (DE)
|
|
218
|
-
|
|
219
|
-
// Prevent auto-conversion (keep original unit)
|
|
220
|
-
distance.formatted(.measurement(width: .wide, usage: .asProvided))
|
|
221
|
-
// US: "5 kilometers" (keeps km even in US locale)
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### Weight / mass
|
|
225
|
-
|
|
226
|
-
```swift
|
|
227
|
-
let weight = Measurement(value: 75, unit: UnitMass.kilograms)
|
|
228
|
-
|
|
229
|
-
weight.formatted(.measurement(width: .wide))
|
|
230
|
-
// US: "165.3 pounds" / DE: "75 Kilogramm"
|
|
231
|
-
|
|
232
|
-
weight.formatted(.measurement(width: .abbreviated, usage: .personWeight))
|
|
233
|
-
// Uses locale-appropriate unit for body weight
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### Temperature
|
|
237
|
-
|
|
238
|
-
```swift
|
|
239
|
-
let temp = Measurement(value: 22, unit: UnitTemperature.celsius)
|
|
240
|
-
|
|
241
|
-
temp.formatted(.measurement(width: .abbreviated))
|
|
242
|
-
// US: "72 F" (auto-converts!) / FR: "22 C" / DE: "22 C"
|
|
243
|
-
|
|
244
|
-
// Weather-specific (ensures locale-correct unit)
|
|
245
|
-
temp.formatted(.measurement(width: .abbreviated, usage: .weather))
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### Speed
|
|
249
|
-
|
|
250
|
-
```swift
|
|
251
|
-
let speed = Measurement(value: 100, unit: UnitSpeed.kilometersPerHour)
|
|
252
|
-
speed.formatted(.measurement(width: .abbreviated))
|
|
253
|
-
// US: "62.1 mph" / DE: "100 km/h"
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### Volume
|
|
257
|
-
|
|
258
|
-
```swift
|
|
259
|
-
let volume = Measurement(value: 500, unit: UnitVolume.milliliters)
|
|
260
|
-
volume.formatted(.measurement(width: .abbreviated, usage: .drink))
|
|
261
|
-
// US: "16.9 fl oz" / DE: "500 ml"
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
## Duration Formatting
|
|
265
|
-
|
|
266
|
-
### Time pattern
|
|
267
|
-
|
|
268
|
-
```swift
|
|
269
|
-
let dur = Duration.seconds(3661) // 1 hour, 1 minute, 1 second
|
|
270
|
-
|
|
271
|
-
dur.formatted(.time(pattern: .hourMinuteSecond)) // "1:01:01"
|
|
272
|
-
dur.formatted(.time(pattern: .hourMinute)) // "1:01"
|
|
273
|
-
dur.formatted(.time(pattern: .minuteSecond)) // "61:01"
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### Units style (iOS 16+)
|
|
277
|
-
|
|
278
|
-
```swift
|
|
279
|
-
dur.formatted(.units(allowed: [.hours, .minutes], width: .wide))
|
|
280
|
-
// "1 hour, 1 minute" (EN) / "1 Stunde, 1 Minute" (DE)
|
|
281
|
-
|
|
282
|
-
dur.formatted(.units(allowed: [.hours, .minutes], width: .abbreviated))
|
|
283
|
-
// "1 hr, 1 min" (EN) / "1 Std., 1 Min." (DE)
|
|
284
|
-
|
|
285
|
-
dur.formatted(.units(allowed: [.hours, .minutes], width: .narrow))
|
|
286
|
-
// "1h 1m"
|
|
287
|
-
|
|
288
|
-
// Maximum unit count
|
|
289
|
-
dur.formatted(.units(allowed: [.hours, .minutes, .seconds],
|
|
290
|
-
width: .abbreviated,
|
|
291
|
-
maximumUnitCount: 2))
|
|
292
|
-
// "1 hr, 1 min" (drops seconds)
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
## PersonNameComponents Formatting
|
|
296
|
-
|
|
297
|
-
Respects locale conventions for name ordering (given-family vs family-given).
|
|
298
|
-
|
|
299
|
-
```swift
|
|
300
|
-
var name = PersonNameComponents()
|
|
301
|
-
name.givenName = "John"
|
|
302
|
-
name.familyName = "Appleseed"
|
|
303
|
-
name.namePrefix = "Dr."
|
|
304
|
-
name.nickname = "Johnny"
|
|
305
|
-
|
|
306
|
-
name.formatted(.name(style: .long)) // "Dr. John Appleseed" (US) / "Appleseed John" (JP)
|
|
307
|
-
name.formatted(.name(style: .medium)) // "John Appleseed" (US) / "Appleseed John" (JP)
|
|
308
|
-
name.formatted(.name(style: .short)) // "John" (US) / "Appleseed" (JP)
|
|
309
|
-
name.formatted(.name(style: .abbreviated)) // "JA" (initials)
|
|
310
|
-
|
|
311
|
-
// In SwiftUI
|
|
312
|
-
Text(name, format: .name(style: .medium))
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
## ByteCountFormatStyle
|
|
316
|
-
|
|
317
|
-
Format file sizes with locale-appropriate units.
|
|
318
|
-
|
|
319
|
-
```swift
|
|
320
|
-
let bytes: Int64 = 1_536_000
|
|
321
|
-
|
|
322
|
-
bytes.formatted(.byteCount(style: .file)) // "1.5 MB"
|
|
323
|
-
bytes.formatted(.byteCount(style: .memory)) // "1.46 MB" (uses 1024-based)
|
|
324
|
-
bytes.formatted(.byteCount(style: .binary)) // "1.46 MB"
|
|
325
|
-
|
|
326
|
-
// Specific allowed units
|
|
327
|
-
bytes.formatted(.byteCount(style: .file, allowedUnits: [.kb])) // "1,536 kB"
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
## ListFormatStyle
|
|
331
|
-
|
|
332
|
-
Join arrays into grammatically correct lists.
|
|
333
|
-
|
|
334
|
-
```swift
|
|
335
|
-
let fruits = ["Apples", "Oranges", "Bananas"]
|
|
336
|
-
|
|
337
|
-
fruits.formatted(.list(type: .and))
|
|
338
|
-
// EN: "Apples, Oranges, and Bananas"
|
|
339
|
-
// FR: "Apples, Oranges et Bananas"
|
|
340
|
-
// AR: "Apples وOranges وBananas"
|
|
341
|
-
|
|
342
|
-
fruits.formatted(.list(type: .or))
|
|
343
|
-
// EN: "Apples, Oranges, or Bananas"
|
|
344
|
-
|
|
345
|
-
// With member formatting
|
|
346
|
-
let prices = [Decimal(1.99), Decimal(2.49), Decimal(3.99)]
|
|
347
|
-
prices.formatted(.list(memberStyle: .currency(code: "USD"), type: .and))
|
|
348
|
-
// "$1.99, $2.49, and $3.99"
|
|
349
|
-
|
|
350
|
-
// Two items
|
|
351
|
-
["Red", "Blue"].formatted(.list(type: .and))
|
|
352
|
-
// "Red and Blue" (no Oxford comma for two items)
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
## Custom FormatStyle Implementation
|
|
356
|
-
|
|
357
|
-
Create a reusable `FormatStyle` for domain-specific formatting.
|
|
358
|
-
|
|
359
|
-
```swift
|
|
360
|
-
struct AbbreviatedCountStyle: FormatStyle {
|
|
361
|
-
func format(_ value: Int) -> String {
|
|
362
|
-
switch value {
|
|
363
|
-
case ..<1_000:
|
|
364
|
-
return "\(value)"
|
|
365
|
-
case 1_000..<1_000_000:
|
|
366
|
-
let k = Double(value) / 1_000.0
|
|
367
|
-
return k.formatted(.number.precision(.fractionLength(0...1))) + "K"
|
|
368
|
-
case 1_000_000..<1_000_000_000:
|
|
369
|
-
let m = Double(value) / 1_000_000.0
|
|
370
|
-
return m.formatted(.number.precision(.fractionLength(0...1))) + "M"
|
|
371
|
-
default:
|
|
372
|
-
let b = Double(value) / 1_000_000_000.0
|
|
373
|
-
return b.formatted(.number.precision(.fractionLength(0...1))) + "B"
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
extension FormatStyle where Self == AbbreviatedCountStyle {
|
|
379
|
-
static var abbreviatedCount: AbbreviatedCountStyle { .init() }
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// Usage
|
|
383
|
-
let followers = 12_500
|
|
384
|
-
followers.formatted(.abbreviatedCount) // "12.5K"
|
|
385
|
-
|
|
386
|
-
// In SwiftUI
|
|
387
|
-
Text(followers, format: .abbreviatedCount)
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### Custom ParseableFormatStyle (for input parsing)
|
|
391
|
-
|
|
392
|
-
```swift
|
|
393
|
-
struct AbbreviatedCountStyle: ParseableFormatStyle {
|
|
394
|
-
var parseStrategy: AbbreviatedCountParseStrategy { .init() }
|
|
395
|
-
|
|
396
|
-
func format(_ value: Int) -> String { /* same as above */ }
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
struct AbbreviatedCountParseStrategy: ParseStrategy {
|
|
400
|
-
func parse(_ value: String) throws -> Int {
|
|
401
|
-
let cleaned = value.uppercased().trimmingCharacters(in: .whitespaces)
|
|
402
|
-
if cleaned.hasSuffix("K") {
|
|
403
|
-
guard let num = Double(cleaned.dropLast()) else { throw parseError }
|
|
404
|
-
return Int(num * 1_000)
|
|
405
|
-
}
|
|
406
|
-
// ... handle M, B, plain numbers
|
|
407
|
-
guard let num = Int(cleaned) else { throw parseError }
|
|
408
|
-
return num
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
private var parseError: some Error {
|
|
412
|
-
DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Invalid count"))
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
## Forcing a Specific Locale
|
|
418
|
-
|
|
419
|
-
Occasionally you need a specific locale (server APIs, fixed-format exports). Use `.locale()` modifier:
|
|
420
|
-
|
|
421
|
-
```swift
|
|
422
|
-
// Force US format for API serialization (not user display)
|
|
423
|
-
let usPrice = price.formatted(.currency(code: "USD").locale(Locale(identifier: "en_US")))
|
|
424
|
-
|
|
425
|
-
// Force a date format for an API
|
|
426
|
-
let apiDate = date.formatted(.iso8601) // Prefer ISO 8601 for APIs
|
|
427
|
-
|
|
428
|
-
// Force German format for a German-language PDF export
|
|
429
|
-
let deDate = date.formatted(.dateTime.month(.wide).day().year().locale(Locale(identifier: "de_DE")))
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
**Warning:** Never force a locale for user-facing UI. Always let the system locale drive user-visible formatting.
|
|
433
|
-
|
|
434
|
-
## RTL Layout Deep Dive
|
|
435
|
-
|
|
436
|
-
### How SwiftUI auto-mirrors
|
|
437
|
-
|
|
438
|
-
SwiftUI respects `layoutDirection` from the environment. When the user's language is RTL:
|
|
439
|
-
|
|
440
|
-
1. **HStack**: Children render right-to-left
|
|
441
|
-
2. **Leading/Trailing**: `.leading` = right side, `.trailing` = left side
|
|
442
|
-
3. **Padding**: `.padding(.leading, 16)` applies to right side
|
|
443
|
-
4. **NavigationStack**: Back button appears on trailing (left) side
|
|
444
|
-
5. **Lists**: Disclosure chevrons point left
|
|
445
|
-
6. **ScrollView**: Horizontal scrolling starts from the right
|
|
446
|
-
7. **Text alignment**: Default alignment follows reading direction
|
|
447
|
-
|
|
448
|
-
### Image flipping
|
|
449
|
-
|
|
450
|
-
```swift
|
|
451
|
-
// Directional images SHOULD flip
|
|
452
|
-
Image(systemName: "chevron.forward")
|
|
453
|
-
.flipsForRightToLeftLayoutDirection(true)
|
|
454
|
-
|
|
455
|
-
Image(systemName: "arrow.right")
|
|
456
|
-
.flipsForRightToLeftLayoutDirection(true)
|
|
457
|
-
|
|
458
|
-
Image("progress-arrow")
|
|
459
|
-
.flipsForRightToLeftLayoutDirection(true)
|
|
460
|
-
|
|
461
|
-
// These should NOT flip:
|
|
462
|
-
// - Logos and brand marks
|
|
463
|
-
// - Photos and illustrations
|
|
464
|
-
// - Clock faces (clockwise is universal)
|
|
465
|
-
// - Music notation
|
|
466
|
-
// - Checkmarks
|
|
467
|
-
// - Mathematical symbols (+, -, =)
|
|
468
|
-
// - Media playback controls (play triangle always points right)
|
|
469
|
-
|
|
470
|
-
// SF Symbols with .rtl variant auto-flip (e.g., text.alignleft has text.alignright)
|
|
471
|
-
// Check SF Symbols app for RTL variants
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
### Environment-based testing
|
|
475
|
-
|
|
476
|
-
```swift
|
|
477
|
-
// Preview with RTL
|
|
478
|
-
#Preview("Arabic RTL") {
|
|
479
|
-
ContentView()
|
|
480
|
-
.environment(\.layoutDirection, .rightToLeft)
|
|
481
|
-
.environment(\.locale, Locale(identifier: "ar"))
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Preview with both directions side by side
|
|
485
|
-
#Preview("LTR vs RTL") {
|
|
486
|
-
HStack(spacing: 0) {
|
|
487
|
-
ContentView()
|
|
488
|
-
.environment(\.layoutDirection, .leftToRight)
|
|
489
|
-
.frame(maxWidth: .infinity)
|
|
490
|
-
Divider()
|
|
491
|
-
ContentView()
|
|
492
|
-
.environment(\.layoutDirection, .rightToLeft)
|
|
493
|
-
.environment(\.locale, Locale(identifier: "ar"))
|
|
494
|
-
.frame(maxWidth: .infinity)
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
### Semantic content attributes (UIKit interop)
|
|
500
|
-
|
|
501
|
-
When mixing UIKit views via `UIViewRepresentable`, set semantic content attribute:
|
|
502
|
-
|
|
503
|
-
```swift
|
|
504
|
-
class MyUIView: UIView {
|
|
505
|
-
override var semanticContentAttribute: UISemanticContentAttribute {
|
|
506
|
-
// .forceLeftToRight for phone numbers, code
|
|
507
|
-
// .forceRightToLeft to force RTL
|
|
508
|
-
// .unspecified to follow system (default)
|
|
509
|
-
.unspecified
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
### Bidirectional text
|
|
515
|
-
|
|
516
|
-
When mixing LTR and RTL text (e.g., English brand names in Arabic text), Unicode bidirectional algorithm handles it automatically. For edge cases:
|
|
517
|
-
|
|
518
|
-
```swift
|
|
519
|
-
// Force LTR for specific content within RTL context
|
|
520
|
-
Text("\u{200E}+1 (555) 123-4567") // LTR mark before phone number
|
|
521
|
-
|
|
522
|
-
// Or use environment override on a specific view
|
|
523
|
-
Text(phoneNumber)
|
|
524
|
-
.environment(\.layoutDirection, .leftToRight)
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
### Common RTL pitfalls
|
|
528
|
-
|
|
529
|
-
| Issue | Wrong | Correct |
|
|
530
|
-
|-------|-------|---------|
|
|
531
|
-
| Fixed position | `.padding(.left, 16)` | `.padding(.leading, 16)` |
|
|
532
|
-
| Absolute offset | `.offset(x: -20)` for "move left" | Use alignment or `.padding(.trailing)` |
|
|
533
|
-
| Text alignment | `.multilineTextAlignment(.left)` | `.multilineTextAlignment(.leading)` |
|
|
534
|
-
| Corner radius | Only rounding top-left/top-right | Round leading/trailing corners |
|
|
535
|
-
| Swipe gestures | "Swipe right to delete" | "Swipe to leading edge" -- or use system gestures |
|
|
536
|
-
|
|
537
|
-
## @ScaledMetric for Dynamic Type
|
|
538
|
-
|
|
539
|
-
Use `@ScaledMetric` to make custom spacing, icon sizes, and padding scale with the user's Dynamic Type setting.
|
|
540
|
-
|
|
541
|
-
```swift
|
|
542
|
-
struct ProfileRow: View {
|
|
543
|
-
@ScaledMetric(relativeTo: .body) private var avatarSize = 44.0
|
|
544
|
-
@ScaledMetric(relativeTo: .body) private var spacing = 12.0
|
|
545
|
-
|
|
546
|
-
var body: some View {
|
|
547
|
-
HStack(spacing: spacing) {
|
|
548
|
-
AvatarView()
|
|
549
|
-
.frame(width: avatarSize, height: avatarSize)
|
|
550
|
-
VStack(alignment: .leading) {
|
|
551
|
-
Text(name).font(.headline)
|
|
552
|
-
Text(subtitle).font(.subheadline)
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
### relativeTo parameter
|
|
560
|
-
|
|
561
|
-
`@ScaledMetric` scales proportionally to a text style. Choose the text style that the metric logically accompanies:
|
|
562
|
-
|
|
563
|
-
| Text style | Base size | Use for |
|
|
564
|
-
|------------|-----------|---------|
|
|
565
|
-
| `.body` | 17pt | General spacing, icons next to body text |
|
|
566
|
-
| `.caption` | 12pt | Small icons, fine spacing |
|
|
567
|
-
| `.title` | 28pt | Large icons, hero spacing |
|
|
568
|
-
| `.largeTitle` | 34pt | Hero images, splash elements |
|
|
569
|
-
|
|
570
|
-
### Testing Dynamic Type
|
|
571
|
-
|
|
572
|
-
```swift
|
|
573
|
-
// Preview with large text
|
|
574
|
-
#Preview("Accessibility XXL") {
|
|
575
|
-
ContentView()
|
|
576
|
-
.dynamicTypeSize(.accessibility3)
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Preview matrix
|
|
580
|
-
#Preview("Dynamic Type Sizes") {
|
|
581
|
-
ScrollView {
|
|
582
|
-
ForEach(DynamicTypeSize.allCases, id: \.self) { size in
|
|
583
|
-
ContentView()
|
|
584
|
-
.dynamicTypeSize(size)
|
|
585
|
-
.padding()
|
|
586
|
-
.border(Color.gray)
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
## Layout Testing with Accessibility Inspector
|
|
593
|
-
|
|
594
|
-
Accessibility Inspector (Xcode > Open Developer Tool > Accessibility Inspector) provides:
|
|
595
|
-
|
|
596
|
-
1. **Audit**: Scans running app for accessibility issues including truncated text
|
|
597
|
-
2. **Inspection**: Shows exact font sizes and Dynamic Type response
|
|
598
|
-
3. **Settings**: Override Dynamic Type size, Bold Text, Reduce Motion on device without changing system settings
|
|
599
|
-
|
|
600
|
-
### Quick test workflow
|
|
601
|
-
|
|
602
|
-
1. Launch app in Simulator
|
|
603
|
-
2. Open Accessibility Inspector, target the Simulator
|
|
604
|
-
3. Use the Settings panel to set Dynamic Type to "Accessibility XXL"
|
|
605
|
-
4. Navigate through every screen -- look for truncated text, overlapping elements, broken layouts
|
|
606
|
-
5. Switch to RTL (set language to Arabic in scheme options)
|
|
607
|
-
6. Repeat navigation -- check all alignment and reading order
|
|
608
|
-
|
|
609
|
-
## Quick Reference Table
|
|
610
|
-
|
|
611
|
-
| Data type | FormatStyle | Example output (US) |
|
|
612
|
-
|-----------|-------------|---------------------|
|
|
613
|
-
| `Date` | `.dateTime.month().day().year()` | "Jan 15, 2026" |
|
|
614
|
-
| `Date` range | `(start..<end).formatted(date:time:)` | "Jan 15 - 20, 2026" |
|
|
615
|
-
| `Date` relative | `.relative(presentation: .named)` | "yesterday" |
|
|
616
|
-
| `Int` | `.number` | "1,234,567" |
|
|
617
|
-
| `Int` ordinal | `.number.notation(.ordinal)` | "3rd" |
|
|
618
|
-
| `Int` compact | `.number.notation(.compactName)` | "1.2M" |
|
|
619
|
-
| `Double` | `.number.precision(.fractionLength(2))` | "3.14" |
|
|
620
|
-
| `Double` | `.percent` | "85.6%" |
|
|
621
|
-
| `Decimal` | `.currency(code: "USD")` | "$29.99" |
|
|
622
|
-
| `Measurement` | `.measurement(width: .abbreviated)` | "5 km" / "3.1 mi" |
|
|
623
|
-
| `Duration` | `.time(pattern: .hourMinuteSecond)` | "1:01:01" |
|
|
624
|
-
| `Duration` | `.units(width: .abbreviated)` | "1 hr, 1 min" |
|
|
625
|
-
| `PersonNameComponents` | `.name(style: .medium)` | "John Doe" |
|
|
626
|
-
| `Int64` (bytes) | `.byteCount(style: .file)` | "1.5 MB" |
|
|
627
|
-
| `[String]` | `.list(type: .and)` | "A, B, and C" |
|
|
1
|
+
# FormatStyle & Locale-Aware Formatting
|
|
2
|
+
|
|
3
|
+
Comprehensive reference for locale-aware formatting in iOS 15+ using `FormatStyle`. Never hard-code date, number, or measurement formats -- these break in every locale except the one you tested.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Date Formatting](#date-formatting)
|
|
8
|
+
- [Number Formatting](#number-formatting)
|
|
9
|
+
- [Measurement Formatting](#measurement-formatting)
|
|
10
|
+
- [Duration Formatting](#duration-formatting)
|
|
11
|
+
- [PersonNameComponents Formatting](#personnamecomponents-formatting)
|
|
12
|
+
- [ByteCountFormatStyle](#bytecountformatstyle)
|
|
13
|
+
- [ListFormatStyle](#listformatstyle)
|
|
14
|
+
- [Custom FormatStyle Implementation](#custom-formatstyle-implementation)
|
|
15
|
+
- [Forcing a Specific Locale](#forcing-a-specific-locale)
|
|
16
|
+
- [RTL Layout Deep Dive](#rtl-layout-deep-dive)
|
|
17
|
+
- [@ScaledMetric for Dynamic Type](#scaledmetric-for-dynamic-type)
|
|
18
|
+
- [Layout Testing with Accessibility Inspector](#layout-testing-with-accessibility-inspector)
|
|
19
|
+
- [Quick Reference Table](#quick-reference-table)
|
|
20
|
+
|
|
21
|
+
## Date Formatting
|
|
22
|
+
|
|
23
|
+
### Preset date and time styles
|
|
24
|
+
|
|
25
|
+
```swift
|
|
26
|
+
let date = Date.now
|
|
27
|
+
|
|
28
|
+
// Date only
|
|
29
|
+
date.formatted(date: .numeric, time: .omitted) // "1/15/2026" (US) / "15.01.2026" (DE)
|
|
30
|
+
date.formatted(date: .abbreviated, time: .omitted) // "Jan 15, 2026" (US) / "15. Jan. 2026" (DE)
|
|
31
|
+
date.formatted(date: .long, time: .omitted) // "January 15, 2026" (US) / "15. Januar 2026" (DE)
|
|
32
|
+
date.formatted(date: .complete, time: .omitted) // "Thursday, January 15, 2026" (US)
|
|
33
|
+
|
|
34
|
+
// Time only
|
|
35
|
+
date.formatted(date: .omitted, time: .shortened) // "3:30 PM" (US) / "15:30" (DE)
|
|
36
|
+
date.formatted(date: .omitted, time: .standard) // "3:30:45 PM" (US) / "15:30:45" (DE)
|
|
37
|
+
date.formatted(date: .omitted, time: .complete) // includes time zone
|
|
38
|
+
|
|
39
|
+
// Combined
|
|
40
|
+
date.formatted(date: .long, time: .shortened) // "January 15, 2026 at 3:30 PM"
|
|
41
|
+
date.formatted() // platform default
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Component-based date formatting
|
|
45
|
+
|
|
46
|
+
Build custom date formats by composing components. The system reorders components for each locale.
|
|
47
|
+
|
|
48
|
+
```swift
|
|
49
|
+
// Month and day
|
|
50
|
+
date.formatted(.dateTime.month().day()) // "Jan 15" (US) / "15 Jan" (UK)
|
|
51
|
+
|
|
52
|
+
// Full date with weekday
|
|
53
|
+
date.formatted(.dateTime.weekday(.wide).month(.wide).day().year())
|
|
54
|
+
// "Thursday, January 15, 2026" (US) / "Donnerstag, 15. Januar 2026" (DE)
|
|
55
|
+
|
|
56
|
+
// Month name styles
|
|
57
|
+
date.formatted(.dateTime.month(.wide)) // "January"
|
|
58
|
+
date.formatted(.dateTime.month(.abbreviated)) // "Jan"
|
|
59
|
+
date.formatted(.dateTime.month(.narrow)) // "J"
|
|
60
|
+
date.formatted(.dateTime.month(.twoDigits)) // "01"
|
|
61
|
+
|
|
62
|
+
// Day styles
|
|
63
|
+
date.formatted(.dateTime.day(.twoDigits)) // "15"
|
|
64
|
+
date.formatted(.dateTime.day(.ordinalOfDayInMonth)) // "3" (third Thursday)
|
|
65
|
+
|
|
66
|
+
// Year
|
|
67
|
+
date.formatted(.dateTime.year(.defaultDigits)) // "2026"
|
|
68
|
+
date.formatted(.dateTime.year(.twoDigits)) // "26"
|
|
69
|
+
|
|
70
|
+
// Hour/minute
|
|
71
|
+
date.formatted(.dateTime.hour().minute()) // "3:30 PM" (US, 12h) / "15:30" (DE, 24h)
|
|
72
|
+
date.formatted(.dateTime.hour(.defaultDigits(amPM: .omitted)).minute()) // "3:30"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### SwiftUI date display
|
|
76
|
+
|
|
77
|
+
```swift
|
|
78
|
+
// Automatic format
|
|
79
|
+
Text(event.date, format: .dateTime.month().day().year())
|
|
80
|
+
|
|
81
|
+
// Date range
|
|
82
|
+
Text(event.start...event.end) // "Jan 15 - Jan 20, 2026"
|
|
83
|
+
|
|
84
|
+
// Relative (auto-updates)
|
|
85
|
+
Text(event.date, style: .relative) // "2 hours ago", "in 3 days"
|
|
86
|
+
Text(event.date, style: .timer) // counts up/down live
|
|
87
|
+
Text(event.date, style: .offset) // "+2 hours" / "-3 days"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Relative date formatting
|
|
91
|
+
|
|
92
|
+
```swift
|
|
93
|
+
// Relative (named style)
|
|
94
|
+
let relative = date.formatted(.relative(presentation: .named))
|
|
95
|
+
// "yesterday", "today", "tomorrow", "last Friday", "in 2 weeks"
|
|
96
|
+
|
|
97
|
+
// Relative (numeric style)
|
|
98
|
+
let relativeNum = date.formatted(.relative(presentation: .numeric))
|
|
99
|
+
// "1 day ago", "in 2 days", "3 weeks ago"
|
|
100
|
+
|
|
101
|
+
// Relative with specific units
|
|
102
|
+
let relativeCustom = date.formatted(.relative(presentation: .named, unitsStyle: .wide))
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Date ranges and intervals
|
|
106
|
+
|
|
107
|
+
```swift
|
|
108
|
+
let start = Date.now
|
|
109
|
+
let end = Calendar.current.date(byAdding: .day, value: 5, to: start)!
|
|
110
|
+
|
|
111
|
+
// Range formatting
|
|
112
|
+
(start..<end).formatted(date: .abbreviated, time: .omitted)
|
|
113
|
+
// "Jan 15 - 20, 2026" (smart about shared month/year)
|
|
114
|
+
|
|
115
|
+
// Duration of interval
|
|
116
|
+
(start..<end).formatted(.components(style: .wide))
|
|
117
|
+
// "5 days"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### ISO 8601 (for APIs, not for display)
|
|
121
|
+
|
|
122
|
+
```swift
|
|
123
|
+
// For serialization to APIs -- NOT for user-facing display
|
|
124
|
+
date.formatted(.iso8601) // "2026-01-15T15:30:45Z"
|
|
125
|
+
date.formatted(.iso8601.dateSeparator(.dash).timeSeparator(.colon))
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Number Formatting
|
|
129
|
+
|
|
130
|
+
### Integer and decimal
|
|
131
|
+
|
|
132
|
+
```swift
|
|
133
|
+
let value = 1234567
|
|
134
|
+
|
|
135
|
+
value.formatted() // "1,234,567" (US) / "1.234.567" (DE) / "1 234 567" (FR)
|
|
136
|
+
value.formatted(.number.grouping(.never)) // "1234567"
|
|
137
|
+
value.formatted(.number.precision(.significantDigits(3))) // "1,230,000"
|
|
138
|
+
|
|
139
|
+
let decimal = 3.14159
|
|
140
|
+
decimal.formatted(.number.precision(.fractionLength(2))) // "3.14"
|
|
141
|
+
decimal.formatted(.number.precision(.fractionLength(0...3))) // "3.142"
|
|
142
|
+
|
|
143
|
+
// Notation
|
|
144
|
+
value.formatted(.number.notation(.compactName)) // "1.2M" (US) / "1,2 Mio." (DE)
|
|
145
|
+
value.formatted(.number.notation(.scientific)) // "1.234567E6"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Rounding
|
|
149
|
+
|
|
150
|
+
```swift
|
|
151
|
+
let num = 3.456
|
|
152
|
+
|
|
153
|
+
// Round to 2 fraction digits
|
|
154
|
+
num.formatted(.number.precision(.fractionLength(2)).rounded(rule: .up)) // "3.46"
|
|
155
|
+
num.formatted(.number.precision(.fractionLength(2)).rounded(rule: .down)) // "3.45"
|
|
156
|
+
num.formatted(.number.precision(.fractionLength(2)).rounded(rule: .toNearestOrEven)) // "3.46"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Percent
|
|
160
|
+
|
|
161
|
+
```swift
|
|
162
|
+
let ratio = 0.856
|
|
163
|
+
|
|
164
|
+
ratio.formatted(.percent) // "86%" (US) / "86 %" (FR)
|
|
165
|
+
ratio.formatted(.percent.precision(.fractionLength(1))) // "85.6%"
|
|
166
|
+
|
|
167
|
+
// Integer percentage
|
|
168
|
+
let score = 92
|
|
169
|
+
score.formatted(.percent) // "9,200%" -- probably not what you want!
|
|
170
|
+
// For integer percentages, divide first:
|
|
171
|
+
(Double(score) / 100).formatted(.percent) // "92%"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Currency
|
|
175
|
+
|
|
176
|
+
Always specify the currency code explicitly. The locale controls formatting (symbol position, decimal separator), but the currency code determines the currency.
|
|
177
|
+
|
|
178
|
+
```swift
|
|
179
|
+
let price = Decimal(29.99)
|
|
180
|
+
|
|
181
|
+
// Explicit currency code (recommended)
|
|
182
|
+
price.formatted(.currency(code: "USD")) // "$29.99" (US) / "29,99 $US" (FR) / "US$29.99" (AU)
|
|
183
|
+
price.formatted(.currency(code: "EUR")) // "EUR29.99" (US) / "29,99 EUR" (DE) / "29,99 EUR" (FR)
|
|
184
|
+
price.formatted(.currency(code: "JPY")) // "JPY30" (no decimals for yen)
|
|
185
|
+
|
|
186
|
+
// Narrow symbol (when space is limited)
|
|
187
|
+
price.formatted(.currency(code: "USD").presentation(.narrow)) // "$29.99" even in non-US locales
|
|
188
|
+
|
|
189
|
+
// In SwiftUI
|
|
190
|
+
Text(price, format: .currency(code: order.currencyCode))
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Important:** Use `Decimal` (not `Double`) for monetary values to avoid floating-point precision errors.
|
|
194
|
+
|
|
195
|
+
### Ordinal numbers
|
|
196
|
+
|
|
197
|
+
```swift
|
|
198
|
+
let position = 3
|
|
199
|
+
position.formatted(.number.notation(.ordinal)) // "3rd" (EN) / "3." (DE) / "3e" (FR)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Measurement Formatting
|
|
203
|
+
|
|
204
|
+
The system auto-converts units based on locale (metric vs imperial) unless you opt out.
|
|
205
|
+
|
|
206
|
+
### Length / distance
|
|
207
|
+
|
|
208
|
+
```swift
|
|
209
|
+
let distance = Measurement(value: 5, unit: UnitLength.kilometers)
|
|
210
|
+
|
|
211
|
+
distance.formatted(.measurement(width: .wide))
|
|
212
|
+
// US: "3.1 miles" (auto-converts to imperial!)
|
|
213
|
+
// DE: "5 Kilometer"
|
|
214
|
+
// JP: "5 km" (with .abbreviated)
|
|
215
|
+
|
|
216
|
+
distance.formatted(.measurement(width: .abbreviated)) // "3.1 mi" (US) / "5 km" (DE)
|
|
217
|
+
distance.formatted(.measurement(width: .narrow)) // "3.1mi" (US) / "5km" (DE)
|
|
218
|
+
|
|
219
|
+
// Prevent auto-conversion (keep original unit)
|
|
220
|
+
distance.formatted(.measurement(width: .wide, usage: .asProvided))
|
|
221
|
+
// US: "5 kilometers" (keeps km even in US locale)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Weight / mass
|
|
225
|
+
|
|
226
|
+
```swift
|
|
227
|
+
let weight = Measurement(value: 75, unit: UnitMass.kilograms)
|
|
228
|
+
|
|
229
|
+
weight.formatted(.measurement(width: .wide))
|
|
230
|
+
// US: "165.3 pounds" / DE: "75 Kilogramm"
|
|
231
|
+
|
|
232
|
+
weight.formatted(.measurement(width: .abbreviated, usage: .personWeight))
|
|
233
|
+
// Uses locale-appropriate unit for body weight
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Temperature
|
|
237
|
+
|
|
238
|
+
```swift
|
|
239
|
+
let temp = Measurement(value: 22, unit: UnitTemperature.celsius)
|
|
240
|
+
|
|
241
|
+
temp.formatted(.measurement(width: .abbreviated))
|
|
242
|
+
// US: "72 F" (auto-converts!) / FR: "22 C" / DE: "22 C"
|
|
243
|
+
|
|
244
|
+
// Weather-specific (ensures locale-correct unit)
|
|
245
|
+
temp.formatted(.measurement(width: .abbreviated, usage: .weather))
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Speed
|
|
249
|
+
|
|
250
|
+
```swift
|
|
251
|
+
let speed = Measurement(value: 100, unit: UnitSpeed.kilometersPerHour)
|
|
252
|
+
speed.formatted(.measurement(width: .abbreviated))
|
|
253
|
+
// US: "62.1 mph" / DE: "100 km/h"
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Volume
|
|
257
|
+
|
|
258
|
+
```swift
|
|
259
|
+
let volume = Measurement(value: 500, unit: UnitVolume.milliliters)
|
|
260
|
+
volume.formatted(.measurement(width: .abbreviated, usage: .drink))
|
|
261
|
+
// US: "16.9 fl oz" / DE: "500 ml"
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Duration Formatting
|
|
265
|
+
|
|
266
|
+
### Time pattern
|
|
267
|
+
|
|
268
|
+
```swift
|
|
269
|
+
let dur = Duration.seconds(3661) // 1 hour, 1 minute, 1 second
|
|
270
|
+
|
|
271
|
+
dur.formatted(.time(pattern: .hourMinuteSecond)) // "1:01:01"
|
|
272
|
+
dur.formatted(.time(pattern: .hourMinute)) // "1:01"
|
|
273
|
+
dur.formatted(.time(pattern: .minuteSecond)) // "61:01"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Units style (iOS 16+)
|
|
277
|
+
|
|
278
|
+
```swift
|
|
279
|
+
dur.formatted(.units(allowed: [.hours, .minutes], width: .wide))
|
|
280
|
+
// "1 hour, 1 minute" (EN) / "1 Stunde, 1 Minute" (DE)
|
|
281
|
+
|
|
282
|
+
dur.formatted(.units(allowed: [.hours, .minutes], width: .abbreviated))
|
|
283
|
+
// "1 hr, 1 min" (EN) / "1 Std., 1 Min." (DE)
|
|
284
|
+
|
|
285
|
+
dur.formatted(.units(allowed: [.hours, .minutes], width: .narrow))
|
|
286
|
+
// "1h 1m"
|
|
287
|
+
|
|
288
|
+
// Maximum unit count
|
|
289
|
+
dur.formatted(.units(allowed: [.hours, .minutes, .seconds],
|
|
290
|
+
width: .abbreviated,
|
|
291
|
+
maximumUnitCount: 2))
|
|
292
|
+
// "1 hr, 1 min" (drops seconds)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## PersonNameComponents Formatting
|
|
296
|
+
|
|
297
|
+
Respects locale conventions for name ordering (given-family vs family-given).
|
|
298
|
+
|
|
299
|
+
```swift
|
|
300
|
+
var name = PersonNameComponents()
|
|
301
|
+
name.givenName = "John"
|
|
302
|
+
name.familyName = "Appleseed"
|
|
303
|
+
name.namePrefix = "Dr."
|
|
304
|
+
name.nickname = "Johnny"
|
|
305
|
+
|
|
306
|
+
name.formatted(.name(style: .long)) // "Dr. John Appleseed" (US) / "Appleseed John" (JP)
|
|
307
|
+
name.formatted(.name(style: .medium)) // "John Appleseed" (US) / "Appleseed John" (JP)
|
|
308
|
+
name.formatted(.name(style: .short)) // "John" (US) / "Appleseed" (JP)
|
|
309
|
+
name.formatted(.name(style: .abbreviated)) // "JA" (initials)
|
|
310
|
+
|
|
311
|
+
// In SwiftUI
|
|
312
|
+
Text(name, format: .name(style: .medium))
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## ByteCountFormatStyle
|
|
316
|
+
|
|
317
|
+
Format file sizes with locale-appropriate units.
|
|
318
|
+
|
|
319
|
+
```swift
|
|
320
|
+
let bytes: Int64 = 1_536_000
|
|
321
|
+
|
|
322
|
+
bytes.formatted(.byteCount(style: .file)) // "1.5 MB"
|
|
323
|
+
bytes.formatted(.byteCount(style: .memory)) // "1.46 MB" (uses 1024-based)
|
|
324
|
+
bytes.formatted(.byteCount(style: .binary)) // "1.46 MB"
|
|
325
|
+
|
|
326
|
+
// Specific allowed units
|
|
327
|
+
bytes.formatted(.byteCount(style: .file, allowedUnits: [.kb])) // "1,536 kB"
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## ListFormatStyle
|
|
331
|
+
|
|
332
|
+
Join arrays into grammatically correct lists.
|
|
333
|
+
|
|
334
|
+
```swift
|
|
335
|
+
let fruits = ["Apples", "Oranges", "Bananas"]
|
|
336
|
+
|
|
337
|
+
fruits.formatted(.list(type: .and))
|
|
338
|
+
// EN: "Apples, Oranges, and Bananas"
|
|
339
|
+
// FR: "Apples, Oranges et Bananas"
|
|
340
|
+
// AR: "Apples وOranges وBananas"
|
|
341
|
+
|
|
342
|
+
fruits.formatted(.list(type: .or))
|
|
343
|
+
// EN: "Apples, Oranges, or Bananas"
|
|
344
|
+
|
|
345
|
+
// With member formatting
|
|
346
|
+
let prices = [Decimal(1.99), Decimal(2.49), Decimal(3.99)]
|
|
347
|
+
prices.formatted(.list(memberStyle: .currency(code: "USD"), type: .and))
|
|
348
|
+
// "$1.99, $2.49, and $3.99"
|
|
349
|
+
|
|
350
|
+
// Two items
|
|
351
|
+
["Red", "Blue"].formatted(.list(type: .and))
|
|
352
|
+
// "Red and Blue" (no Oxford comma for two items)
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Custom FormatStyle Implementation
|
|
356
|
+
|
|
357
|
+
Create a reusable `FormatStyle` for domain-specific formatting.
|
|
358
|
+
|
|
359
|
+
```swift
|
|
360
|
+
struct AbbreviatedCountStyle: FormatStyle {
|
|
361
|
+
func format(_ value: Int) -> String {
|
|
362
|
+
switch value {
|
|
363
|
+
case ..<1_000:
|
|
364
|
+
return "\(value)"
|
|
365
|
+
case 1_000..<1_000_000:
|
|
366
|
+
let k = Double(value) / 1_000.0
|
|
367
|
+
return k.formatted(.number.precision(.fractionLength(0...1))) + "K"
|
|
368
|
+
case 1_000_000..<1_000_000_000:
|
|
369
|
+
let m = Double(value) / 1_000_000.0
|
|
370
|
+
return m.formatted(.number.precision(.fractionLength(0...1))) + "M"
|
|
371
|
+
default:
|
|
372
|
+
let b = Double(value) / 1_000_000_000.0
|
|
373
|
+
return b.formatted(.number.precision(.fractionLength(0...1))) + "B"
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
extension FormatStyle where Self == AbbreviatedCountStyle {
|
|
379
|
+
static var abbreviatedCount: AbbreviatedCountStyle { .init() }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Usage
|
|
383
|
+
let followers = 12_500
|
|
384
|
+
followers.formatted(.abbreviatedCount) // "12.5K"
|
|
385
|
+
|
|
386
|
+
// In SwiftUI
|
|
387
|
+
Text(followers, format: .abbreviatedCount)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Custom ParseableFormatStyle (for input parsing)
|
|
391
|
+
|
|
392
|
+
```swift
|
|
393
|
+
struct AbbreviatedCountStyle: ParseableFormatStyle {
|
|
394
|
+
var parseStrategy: AbbreviatedCountParseStrategy { .init() }
|
|
395
|
+
|
|
396
|
+
func format(_ value: Int) -> String { /* same as above */ }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
struct AbbreviatedCountParseStrategy: ParseStrategy {
|
|
400
|
+
func parse(_ value: String) throws -> Int {
|
|
401
|
+
let cleaned = value.uppercased().trimmingCharacters(in: .whitespaces)
|
|
402
|
+
if cleaned.hasSuffix("K") {
|
|
403
|
+
guard let num = Double(cleaned.dropLast()) else { throw parseError }
|
|
404
|
+
return Int(num * 1_000)
|
|
405
|
+
}
|
|
406
|
+
// ... handle M, B, plain numbers
|
|
407
|
+
guard let num = Int(cleaned) else { throw parseError }
|
|
408
|
+
return num
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private var parseError: some Error {
|
|
412
|
+
DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Invalid count"))
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Forcing a Specific Locale
|
|
418
|
+
|
|
419
|
+
Occasionally you need a specific locale (server APIs, fixed-format exports). Use `.locale()` modifier:
|
|
420
|
+
|
|
421
|
+
```swift
|
|
422
|
+
// Force US format for API serialization (not user display)
|
|
423
|
+
let usPrice = price.formatted(.currency(code: "USD").locale(Locale(identifier: "en_US")))
|
|
424
|
+
|
|
425
|
+
// Force a date format for an API
|
|
426
|
+
let apiDate = date.formatted(.iso8601) // Prefer ISO 8601 for APIs
|
|
427
|
+
|
|
428
|
+
// Force German format for a German-language PDF export
|
|
429
|
+
let deDate = date.formatted(.dateTime.month(.wide).day().year().locale(Locale(identifier: "de_DE")))
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Warning:** Never force a locale for user-facing UI. Always let the system locale drive user-visible formatting.
|
|
433
|
+
|
|
434
|
+
## RTL Layout Deep Dive
|
|
435
|
+
|
|
436
|
+
### How SwiftUI auto-mirrors
|
|
437
|
+
|
|
438
|
+
SwiftUI respects `layoutDirection` from the environment. When the user's language is RTL:
|
|
439
|
+
|
|
440
|
+
1. **HStack**: Children render right-to-left
|
|
441
|
+
2. **Leading/Trailing**: `.leading` = right side, `.trailing` = left side
|
|
442
|
+
3. **Padding**: `.padding(.leading, 16)` applies to right side
|
|
443
|
+
4. **NavigationStack**: Back button appears on trailing (left) side
|
|
444
|
+
5. **Lists**: Disclosure chevrons point left
|
|
445
|
+
6. **ScrollView**: Horizontal scrolling starts from the right
|
|
446
|
+
7. **Text alignment**: Default alignment follows reading direction
|
|
447
|
+
|
|
448
|
+
### Image flipping
|
|
449
|
+
|
|
450
|
+
```swift
|
|
451
|
+
// Directional images SHOULD flip
|
|
452
|
+
Image(systemName: "chevron.forward")
|
|
453
|
+
.flipsForRightToLeftLayoutDirection(true)
|
|
454
|
+
|
|
455
|
+
Image(systemName: "arrow.right")
|
|
456
|
+
.flipsForRightToLeftLayoutDirection(true)
|
|
457
|
+
|
|
458
|
+
Image("progress-arrow")
|
|
459
|
+
.flipsForRightToLeftLayoutDirection(true)
|
|
460
|
+
|
|
461
|
+
// These should NOT flip:
|
|
462
|
+
// - Logos and brand marks
|
|
463
|
+
// - Photos and illustrations
|
|
464
|
+
// - Clock faces (clockwise is universal)
|
|
465
|
+
// - Music notation
|
|
466
|
+
// - Checkmarks
|
|
467
|
+
// - Mathematical symbols (+, -, =)
|
|
468
|
+
// - Media playback controls (play triangle always points right)
|
|
469
|
+
|
|
470
|
+
// SF Symbols with .rtl variant auto-flip (e.g., text.alignleft has text.alignright)
|
|
471
|
+
// Check SF Symbols app for RTL variants
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Environment-based testing
|
|
475
|
+
|
|
476
|
+
```swift
|
|
477
|
+
// Preview with RTL
|
|
478
|
+
#Preview("Arabic RTL") {
|
|
479
|
+
ContentView()
|
|
480
|
+
.environment(\.layoutDirection, .rightToLeft)
|
|
481
|
+
.environment(\.locale, Locale(identifier: "ar"))
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Preview with both directions side by side
|
|
485
|
+
#Preview("LTR vs RTL") {
|
|
486
|
+
HStack(spacing: 0) {
|
|
487
|
+
ContentView()
|
|
488
|
+
.environment(\.layoutDirection, .leftToRight)
|
|
489
|
+
.frame(maxWidth: .infinity)
|
|
490
|
+
Divider()
|
|
491
|
+
ContentView()
|
|
492
|
+
.environment(\.layoutDirection, .rightToLeft)
|
|
493
|
+
.environment(\.locale, Locale(identifier: "ar"))
|
|
494
|
+
.frame(maxWidth: .infinity)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Semantic content attributes (UIKit interop)
|
|
500
|
+
|
|
501
|
+
When mixing UIKit views via `UIViewRepresentable`, set semantic content attribute:
|
|
502
|
+
|
|
503
|
+
```swift
|
|
504
|
+
class MyUIView: UIView {
|
|
505
|
+
override var semanticContentAttribute: UISemanticContentAttribute {
|
|
506
|
+
// .forceLeftToRight for phone numbers, code
|
|
507
|
+
// .forceRightToLeft to force RTL
|
|
508
|
+
// .unspecified to follow system (default)
|
|
509
|
+
.unspecified
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Bidirectional text
|
|
515
|
+
|
|
516
|
+
When mixing LTR and RTL text (e.g., English brand names in Arabic text), Unicode bidirectional algorithm handles it automatically. For edge cases:
|
|
517
|
+
|
|
518
|
+
```swift
|
|
519
|
+
// Force LTR for specific content within RTL context
|
|
520
|
+
Text("\u{200E}+1 (555) 123-4567") // LTR mark before phone number
|
|
521
|
+
|
|
522
|
+
// Or use environment override on a specific view
|
|
523
|
+
Text(phoneNumber)
|
|
524
|
+
.environment(\.layoutDirection, .leftToRight)
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Common RTL pitfalls
|
|
528
|
+
|
|
529
|
+
| Issue | Wrong | Correct |
|
|
530
|
+
|-------|-------|---------|
|
|
531
|
+
| Fixed position | `.padding(.left, 16)` | `.padding(.leading, 16)` |
|
|
532
|
+
| Absolute offset | `.offset(x: -20)` for "move left" | Use alignment or `.padding(.trailing)` |
|
|
533
|
+
| Text alignment | `.multilineTextAlignment(.left)` | `.multilineTextAlignment(.leading)` |
|
|
534
|
+
| Corner radius | Only rounding top-left/top-right | Round leading/trailing corners |
|
|
535
|
+
| Swipe gestures | "Swipe right to delete" | "Swipe to leading edge" -- or use system gestures |
|
|
536
|
+
|
|
537
|
+
## @ScaledMetric for Dynamic Type
|
|
538
|
+
|
|
539
|
+
Use `@ScaledMetric` to make custom spacing, icon sizes, and padding scale with the user's Dynamic Type setting.
|
|
540
|
+
|
|
541
|
+
```swift
|
|
542
|
+
struct ProfileRow: View {
|
|
543
|
+
@ScaledMetric(relativeTo: .body) private var avatarSize = 44.0
|
|
544
|
+
@ScaledMetric(relativeTo: .body) private var spacing = 12.0
|
|
545
|
+
|
|
546
|
+
var body: some View {
|
|
547
|
+
HStack(spacing: spacing) {
|
|
548
|
+
AvatarView()
|
|
549
|
+
.frame(width: avatarSize, height: avatarSize)
|
|
550
|
+
VStack(alignment: .leading) {
|
|
551
|
+
Text(name).font(.headline)
|
|
552
|
+
Text(subtitle).font(.subheadline)
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### relativeTo parameter
|
|
560
|
+
|
|
561
|
+
`@ScaledMetric` scales proportionally to a text style. Choose the text style that the metric logically accompanies:
|
|
562
|
+
|
|
563
|
+
| Text style | Base size | Use for |
|
|
564
|
+
|------------|-----------|---------|
|
|
565
|
+
| `.body` | 17pt | General spacing, icons next to body text |
|
|
566
|
+
| `.caption` | 12pt | Small icons, fine spacing |
|
|
567
|
+
| `.title` | 28pt | Large icons, hero spacing |
|
|
568
|
+
| `.largeTitle` | 34pt | Hero images, splash elements |
|
|
569
|
+
|
|
570
|
+
### Testing Dynamic Type
|
|
571
|
+
|
|
572
|
+
```swift
|
|
573
|
+
// Preview with large text
|
|
574
|
+
#Preview("Accessibility XXL") {
|
|
575
|
+
ContentView()
|
|
576
|
+
.dynamicTypeSize(.accessibility3)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Preview matrix
|
|
580
|
+
#Preview("Dynamic Type Sizes") {
|
|
581
|
+
ScrollView {
|
|
582
|
+
ForEach(DynamicTypeSize.allCases, id: \.self) { size in
|
|
583
|
+
ContentView()
|
|
584
|
+
.dynamicTypeSize(size)
|
|
585
|
+
.padding()
|
|
586
|
+
.border(Color.gray)
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
## Layout Testing with Accessibility Inspector
|
|
593
|
+
|
|
594
|
+
Accessibility Inspector (Xcode > Open Developer Tool > Accessibility Inspector) provides:
|
|
595
|
+
|
|
596
|
+
1. **Audit**: Scans running app for accessibility issues including truncated text
|
|
597
|
+
2. **Inspection**: Shows exact font sizes and Dynamic Type response
|
|
598
|
+
3. **Settings**: Override Dynamic Type size, Bold Text, Reduce Motion on device without changing system settings
|
|
599
|
+
|
|
600
|
+
### Quick test workflow
|
|
601
|
+
|
|
602
|
+
1. Launch app in Simulator
|
|
603
|
+
2. Open Accessibility Inspector, target the Simulator
|
|
604
|
+
3. Use the Settings panel to set Dynamic Type to "Accessibility XXL"
|
|
605
|
+
4. Navigate through every screen -- look for truncated text, overlapping elements, broken layouts
|
|
606
|
+
5. Switch to RTL (set language to Arabic in scheme options)
|
|
607
|
+
6. Repeat navigation -- check all alignment and reading order
|
|
608
|
+
|
|
609
|
+
## Quick Reference Table
|
|
610
|
+
|
|
611
|
+
| Data type | FormatStyle | Example output (US) |
|
|
612
|
+
|-----------|-------------|---------------------|
|
|
613
|
+
| `Date` | `.dateTime.month().day().year()` | "Jan 15, 2026" |
|
|
614
|
+
| `Date` range | `(start..<end).formatted(date:time:)` | "Jan 15 - 20, 2026" |
|
|
615
|
+
| `Date` relative | `.relative(presentation: .named)` | "yesterday" |
|
|
616
|
+
| `Int` | `.number` | "1,234,567" |
|
|
617
|
+
| `Int` ordinal | `.number.notation(.ordinal)` | "3rd" |
|
|
618
|
+
| `Int` compact | `.number.notation(.compactName)` | "1.2M" |
|
|
619
|
+
| `Double` | `.number.precision(.fractionLength(2))` | "3.14" |
|
|
620
|
+
| `Double` | `.percent` | "85.6%" |
|
|
621
|
+
| `Decimal` | `.currency(code: "USD")` | "$29.99" |
|
|
622
|
+
| `Measurement` | `.measurement(width: .abbreviated)` | "5 km" / "3.1 mi" |
|
|
623
|
+
| `Duration` | `.time(pattern: .hourMinuteSecond)` | "1:01:01" |
|
|
624
|
+
| `Duration` | `.units(width: .abbreviated)` | "1 hr, 1 min" |
|
|
625
|
+
| `PersonNameComponents` | `.name(style: .medium)` | "John Doe" |
|
|
626
|
+
| `Int64` (bytes) | `.byteCount(style: .file)` | "1.5 MB" |
|
|
627
|
+
| `[String]` | `.list(type: .and)` | "A, B, and C" |
|