@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,212 +1,249 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: swiftui-patterns
|
|
3
|
-
description: "
|
|
3
|
+
description: "SwiftUI Model-View structure: @Observable ownership, @State, @Bindable, @Environment, view decomposition, custom ViewModifiers, .task loading, iOS 26 APIs, Writing Tools. Use when structuring a SwiftUI app, managing state, composing or splitting views, or adopting iOS 26 APIs. Not for file-by-file review (swiftui-pro) or single-screen wiring (swiftui-ui-patterns)."
|
|
4
4
|
metadata:
|
|
5
|
-
source:
|
|
5
|
+
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# SwiftUI Patterns
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
How to shape a SwiftUI code base so views stay small, state has one clear
|
|
11
|
+
owner, and business logic lives outside `body`. Examples target iOS 26 and
|
|
12
|
+
Swift 6.3. Everything here also compiles for an iOS 17 deployment target unless
|
|
13
|
+
a line names a newer release.
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- [Architecture: Model-View (MV) Pattern](#architecture-model-view-mv-pattern)
|
|
15
|
-
- [State Management](#state-management)
|
|
16
|
-
- [View Ordering Convention](#view-ordering-convention)
|
|
17
|
-
- [View Composition](#view-composition)
|
|
18
|
-
- [Environment](#environment)
|
|
19
|
-
- [Async Data Loading](#async-data-loading)
|
|
20
|
-
- [iOS 26+ New APIs](#ios-26-new-apis)
|
|
21
|
-
- [Performance Guidelines](#performance-guidelines)
|
|
22
|
-
- [HIG Alignment](#hig-alignment)
|
|
23
|
-
- [Writing Tools (iOS 18+)](#writing-tools-ios-18)
|
|
24
|
-
- [Common Mistakes](#common-mistakes)
|
|
25
|
-
- [Review Checklist](#review-checklist)
|
|
26
|
-
- [References](#references)
|
|
27
|
-
|
|
28
|
-
**Scope boundary:** This skill covers architecture, state ownership, composition, environment wiring, async loading, and related SwiftUI app structure patterns. Detailed navigation patterns are covered in the `swiftui-navigation` skill, including `NavigationStack`, `NavigationSplitView`, sheets, tabs, and deep-linking patterns. Detailed layout, container, and component patterns are covered in the `swiftui-layout-components` skill, including stacks, grids, lists, scroll view patterns, forms, controls, search UI with `.searchable`, overlays, and related layout components.
|
|
15
|
+
Two neighbouring topics are handled elsewhere on purpose:
|
|
29
16
|
|
|
30
|
-
|
|
17
|
+
- Navigation (`NavigationStack`, `NavigationSplitView`, sheets, tabs, deep
|
|
18
|
+
links): `swiftui-navigation`.
|
|
19
|
+
- Layout and controls (stacks, grids, lists, scroll views, forms, `.searchable`,
|
|
20
|
+
overlays): `swiftui-layout-components`.
|
|
31
21
|
|
|
32
|
-
|
|
22
|
+
## Contents
|
|
33
23
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
1. [Model-View by Default](#1-model-view-by-default)
|
|
25
|
+
2. [Owning and Passing Observable State](#2-owning-and-passing-observable-state)
|
|
26
|
+
3. [Member Order Inside a View](#3-member-order-inside-a-view)
|
|
27
|
+
4. [Splitting Views Up](#4-splitting-views-up)
|
|
28
|
+
5. [Environment Values](#5-environment-values)
|
|
29
|
+
6. [Loading Data with .task](#6-loading-data-with-task)
|
|
30
|
+
7. [iOS 26 Additions](#7-ios-26-additions)
|
|
31
|
+
8. [Performance](#8-performance)
|
|
32
|
+
9. [Following the HIG](#9-following-the-hig)
|
|
33
|
+
10. [Writing Tools (iOS 18+)](#10-writing-tools-ios-18)
|
|
34
|
+
11. [Common Mistakes](#11-common-mistakes)
|
|
35
|
+
12. [Review Checklist](#12-review-checklist)
|
|
36
|
+
13. [References](#13-references)
|
|
37
|
+
|
|
38
|
+
## 1. Model-View by Default
|
|
39
|
+
|
|
40
|
+
A view describes what the screen looks like for a given state. Models and
|
|
41
|
+
services own the rules, the networking and the persistence. That split is the
|
|
42
|
+
Model-View (MV) approach, and it is the default here.
|
|
43
|
+
|
|
44
|
+
- Do not introduce a view model unless the project already uses them.
|
|
45
|
+
- Let SwiftUI coordinate the flow: `@State`, `@Environment`, `@Query`, `.task`
|
|
46
|
+
and `.onChange` are the orchestration tools.
|
|
47
|
+
- Put services and shared models in the environment. Keep each view small and
|
|
48
|
+
composable.
|
|
49
|
+
- A growing view gets split into subviews, not given a view model.
|
|
50
|
+
- Unit tests target models, services and business rules. Views stay declarative
|
|
51
|
+
enough that they do not need their own tests.
|
|
39
52
|
|
|
40
53
|
```swift
|
|
41
|
-
struct
|
|
42
|
-
@Environment(
|
|
54
|
+
struct RecipeListView: View {
|
|
55
|
+
@Environment(RecipeService.self) private var service
|
|
43
56
|
|
|
44
|
-
enum
|
|
45
|
-
case
|
|
57
|
+
enum Phase {
|
|
58
|
+
case fetching
|
|
59
|
+
case failed(String)
|
|
60
|
+
case ready([Recipe])
|
|
46
61
|
}
|
|
47
62
|
|
|
48
|
-
@State private var
|
|
63
|
+
@State private var phase: Phase = .fetching
|
|
49
64
|
|
|
50
65
|
var body: some View {
|
|
51
66
|
List {
|
|
52
|
-
switch
|
|
53
|
-
case .
|
|
67
|
+
switch phase {
|
|
68
|
+
case .fetching:
|
|
54
69
|
ProgressView()
|
|
55
|
-
case .
|
|
56
|
-
ContentUnavailableView("
|
|
70
|
+
case .failed(let message):
|
|
71
|
+
ContentUnavailableView("Recipes unavailable",
|
|
72
|
+
systemImage: "exclamationmark.triangle",
|
|
57
73
|
description: Text(message))
|
|
58
|
-
case .
|
|
59
|
-
ForEach(
|
|
60
|
-
PostRow(post: post)
|
|
61
|
-
}
|
|
74
|
+
case .ready(let recipes):
|
|
75
|
+
ForEach(recipes) { RecipeRow(recipe: $0) }
|
|
62
76
|
}
|
|
63
77
|
}
|
|
64
|
-
.task { await
|
|
65
|
-
.refreshable { await
|
|
78
|
+
.task { await reload() }
|
|
79
|
+
.refreshable { await reload() }
|
|
66
80
|
}
|
|
67
81
|
|
|
68
|
-
private func
|
|
82
|
+
private func reload() async {
|
|
69
83
|
do {
|
|
70
|
-
|
|
71
|
-
viewState = .loaded(posts)
|
|
84
|
+
phase = .ready(try await service.recipes())
|
|
72
85
|
} catch {
|
|
73
|
-
|
|
86
|
+
phase = .failed(error.localizedDescription)
|
|
74
87
|
}
|
|
75
88
|
}
|
|
76
89
|
}
|
|
77
90
|
```
|
|
78
91
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
## State Management
|
|
92
|
+
Why MV, when a view model is still justified, and how to wire an app shell:
|
|
93
|
+
[references/architecture-patterns.md](references/architecture-patterns.md).
|
|
82
94
|
|
|
83
|
-
|
|
95
|
+
## 2. Owning and Passing Observable State
|
|
84
96
|
|
|
85
|
-
|
|
97
|
+
Mark every `@Observable` class that backs UI with `@MainActor`. Its properties
|
|
98
|
+
are read by views on the main actor, and Swift 6 strict concurrency rejects
|
|
99
|
+
unisolated mutation.
|
|
86
100
|
|
|
87
|
-
|
|
|
88
|
-
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
| `@Environment(
|
|
93
|
-
|
|
|
94
|
-
|
|
|
101
|
+
| Situation | Declaration |
|
|
102
|
+
|---|---|
|
|
103
|
+
| The view creates the object and controls its lifetime | `@State private var store = Store()` |
|
|
104
|
+
| The view gets an `@Observable` object and only reads it | `let store: Store` (no wrapper) |
|
|
105
|
+
| The view gets an `@Observable` object and needs `$` bindings | `@Bindable var store: Store` |
|
|
106
|
+
| The object is shared through the environment | `@Environment(Store.self) private var store` |
|
|
107
|
+
| Simple view-local value (flag, counter, field text) | `@State private var isExpanded = false` |
|
|
108
|
+
| Two-way link to a parent's `@State` or `@Bindable` property | `@Binding var isExpanded: Bool` |
|
|
95
109
|
|
|
96
|
-
|
|
110
|
+
`@State` for values is always `private`: nobody outside the view should set it.
|
|
97
111
|
|
|
98
112
|
```swift
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
var
|
|
103
|
-
var
|
|
113
|
+
@MainActor @Observable
|
|
114
|
+
final class PlaylistStore {
|
|
115
|
+
var name = ""
|
|
116
|
+
var tracks: [Track] = []
|
|
117
|
+
var isSyncing = false
|
|
104
118
|
}
|
|
105
119
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
@State var viewModel = ItemStore()
|
|
120
|
+
struct PlaylistScreen: View {
|
|
121
|
+
@State private var store = PlaylistStore()
|
|
109
122
|
|
|
110
123
|
var body: some View {
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
VStack {
|
|
125
|
+
PlaylistHeader(store: store)
|
|
126
|
+
PlaylistNameField(store: store)
|
|
127
|
+
PlaylistFooter()
|
|
128
|
+
}
|
|
129
|
+
.environment(store)
|
|
113
130
|
}
|
|
114
131
|
}
|
|
115
132
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
var body: some View { Text(store.title) }
|
|
133
|
+
struct PlaylistHeader: View {
|
|
134
|
+
let store: PlaylistStore
|
|
135
|
+
var body: some View { Text(store.name) }
|
|
121
136
|
}
|
|
122
137
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
var body: some View {
|
|
128
|
-
TextField("Title", text: $store.title)
|
|
129
|
-
}
|
|
138
|
+
struct PlaylistNameField: View {
|
|
139
|
+
@Bindable var store: PlaylistStore
|
|
140
|
+
var body: some View { TextField("Name", text: $store.name) }
|
|
130
141
|
}
|
|
131
142
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
@Environment(ItemStore.self) var store
|
|
143
|
+
struct PlaylistFooter: View {
|
|
144
|
+
@Environment(PlaylistStore.self) private var store
|
|
135
145
|
|
|
136
146
|
var body: some View {
|
|
137
|
-
@Bindable var
|
|
138
|
-
|
|
147
|
+
@Bindable var editable = store
|
|
148
|
+
Toggle("Syncing", isOn: $editable.isSyncing)
|
|
139
149
|
}
|
|
140
150
|
}
|
|
141
151
|
```
|
|
142
152
|
|
|
143
|
-
|
|
153
|
+
The last view shows the trick for environment objects: declare a local
|
|
154
|
+
`@Bindable` copy inside `body` to get `$` bindings.
|
|
144
155
|
|
|
145
|
-
|
|
156
|
+
Observation is tracked per property. `PlaylistHeader` reads only `name`, so a
|
|
157
|
+
change to `tracks` or `isSyncing` does not re-evaluate it. `ObservableObject`
|
|
158
|
+
invalidates every subscriber on any `@Published` change, so this alone removes
|
|
159
|
+
a large share of wasted updates.
|
|
146
160
|
|
|
147
|
-
|
|
161
|
+
### ObservableObject in older targets
|
|
148
162
|
|
|
149
|
-
|
|
163
|
+
Keep `ObservableObject` only for a deployment target of iOS 16 or earlier. When
|
|
164
|
+
moving to Observation:
|
|
150
165
|
|
|
151
|
-
|
|
166
|
+
| Before | After |
|
|
167
|
+
|---|---|
|
|
168
|
+
| `@StateObject` | `@State` |
|
|
169
|
+
| `@ObservedObject` | `let` (or `@Bindable` for bindings) |
|
|
170
|
+
| `@EnvironmentObject` | `@Environment(Type.self)` |
|
|
152
171
|
|
|
153
|
-
## View
|
|
172
|
+
## 3. Member Order Inside a View
|
|
154
173
|
|
|
155
|
-
|
|
174
|
+
Keep the same top-to-bottom order in every view file:
|
|
156
175
|
|
|
157
|
-
|
|
176
|
+
1. `@Environment` properties
|
|
177
|
+
2. `let` properties
|
|
178
|
+
3. `@State` and other stored properties
|
|
179
|
+
4. computed `var`s
|
|
180
|
+
5. `init`
|
|
181
|
+
6. `body`
|
|
182
|
+
7. view builders and other helpers
|
|
183
|
+
8. `async` functions
|
|
184
|
+
|
|
185
|
+
## 4. Splitting Views Up
|
|
186
|
+
|
|
187
|
+
### Subview structs
|
|
188
|
+
|
|
189
|
+
Give each piece one job and pass it plain values and closures:
|
|
158
190
|
|
|
159
191
|
```swift
|
|
160
192
|
var body: some View {
|
|
161
|
-
VStack {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
193
|
+
VStack(alignment: .leading) {
|
|
194
|
+
TicketSummary(event: event, seat: seat)
|
|
195
|
+
TicketBarcode(code: ticket.code)
|
|
196
|
+
TicketActions(onTransfer: transfer, onRefund: refund)
|
|
165
197
|
}
|
|
166
198
|
}
|
|
167
199
|
```
|
|
168
200
|
|
|
169
|
-
### Computed
|
|
201
|
+
### Computed view properties
|
|
170
202
|
|
|
171
|
-
|
|
203
|
+
Pieces that only make sense in one file can stay as private computed
|
|
204
|
+
properties. Promote one to its own `View` struct once it is reused elsewhere or
|
|
205
|
+
needs its own state.
|
|
172
206
|
|
|
173
207
|
```swift
|
|
174
208
|
var body: some View {
|
|
175
209
|
List {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
210
|
+
banner
|
|
211
|
+
sortControls
|
|
212
|
+
matches
|
|
179
213
|
}
|
|
180
214
|
}
|
|
181
215
|
|
|
182
|
-
private var
|
|
216
|
+
private var banner: some View {
|
|
183
217
|
VStack(alignment: .leading) {
|
|
184
|
-
Text(
|
|
185
|
-
Text(
|
|
218
|
+
Text(league.name).font(.title2)
|
|
219
|
+
Text(league.season).font(.subheadline)
|
|
186
220
|
}
|
|
187
221
|
}
|
|
188
222
|
```
|
|
189
223
|
|
|
190
|
-
### ViewBuilder
|
|
224
|
+
### @ViewBuilder functions
|
|
191
225
|
|
|
192
|
-
|
|
226
|
+
Branching that does not justify a struct fits a private builder function:
|
|
193
227
|
|
|
194
228
|
```swift
|
|
195
229
|
@ViewBuilder
|
|
196
|
-
private func
|
|
197
|
-
switch
|
|
198
|
-
case .
|
|
199
|
-
|
|
230
|
+
private func deliveryLabel(for state: DeliveryState) -> some View {
|
|
231
|
+
switch state {
|
|
232
|
+
case .delivered:
|
|
233
|
+
Text("Delivered").foregroundStyle(.green)
|
|
234
|
+
case .inTransit, .pending:
|
|
235
|
+
Text("On the way").foregroundStyle(.secondary)
|
|
200
236
|
}
|
|
201
237
|
}
|
|
202
238
|
```
|
|
203
239
|
|
|
204
|
-
### Custom
|
|
240
|
+
### Custom view modifiers
|
|
205
241
|
|
|
206
|
-
|
|
242
|
+
When the same styling appears in several places, move it into a
|
|
243
|
+
`ViewModifier` and expose a `View` extension:
|
|
207
244
|
|
|
208
245
|
```swift
|
|
209
|
-
struct
|
|
246
|
+
struct TileBackground: ViewModifier {
|
|
210
247
|
func body(content: Content) -> some View {
|
|
211
248
|
content
|
|
212
249
|
.padding()
|
|
@@ -215,185 +252,212 @@ struct CardStyle: ViewModifier {
|
|
|
215
252
|
.shadow(radius: 2)
|
|
216
253
|
}
|
|
217
254
|
}
|
|
218
|
-
|
|
255
|
+
|
|
256
|
+
extension View {
|
|
257
|
+
func tileBackground() -> some View { modifier(TileBackground()) }
|
|
258
|
+
}
|
|
219
259
|
```
|
|
220
260
|
|
|
221
|
-
###
|
|
261
|
+
### Keep the tree stable
|
|
222
262
|
|
|
223
|
-
|
|
263
|
+
Do not swap entire view hierarchies with an `if` at the top of `body`. Keep one
|
|
264
|
+
base view and move conditions into sections or modifier arguments, so SwiftUI
|
|
265
|
+
keeps identity and state. Once a view file passes roughly 300 lines, split it
|
|
266
|
+
into extensions separated by `// MARK: -`.
|
|
224
267
|
|
|
225
|
-
## Environment
|
|
268
|
+
## 5. Environment Values
|
|
226
269
|
|
|
227
|
-
### Custom
|
|
270
|
+
### Custom values with @Entry
|
|
228
271
|
|
|
229
|
-
|
|
272
|
+
Declare custom values and actions with the `@Entry` macro inside an
|
|
273
|
+
`EnvironmentValues` extension. The macro writes the key type for you.
|
|
230
274
|
|
|
231
275
|
```swift
|
|
232
276
|
extension EnvironmentValues {
|
|
233
|
-
@Entry var
|
|
234
|
-
@Entry var
|
|
277
|
+
@Entry var palette: Palette = .standard
|
|
278
|
+
@Entry var syncNow: @Sendable () async -> Void = {}
|
|
235
279
|
}
|
|
236
280
|
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
.environment(\.
|
|
281
|
+
// Supplying values
|
|
282
|
+
InboxView()
|
|
283
|
+
.environment(\.palette, .highContrast)
|
|
284
|
+
.environment(\.syncNow) { await mailbox.sync() }
|
|
240
285
|
|
|
241
|
-
|
|
242
|
-
@Environment(\.
|
|
286
|
+
// Reading them
|
|
287
|
+
@Environment(\.palette) private var palette
|
|
288
|
+
@Environment(\.syncNow) private var syncNow
|
|
243
289
|
```
|
|
244
290
|
|
|
245
|
-
|
|
291
|
+
`@Entry` is a macro that ships with the Xcode 16 SDK and expands to an ordinary
|
|
292
|
+
`EnvironmentKey`, so it works for any deployment target, iOS 17 included. Write
|
|
293
|
+
a manual `EnvironmentKey` conformance only when the project still builds with
|
|
294
|
+
an older Xcode.
|
|
246
295
|
|
|
247
|
-
###
|
|
296
|
+
### Built-in values used most often
|
|
248
297
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
@Environment(\.colorScheme) var colorScheme
|
|
252
|
-
@Environment(\.dynamicTypeSize) var dynamicTypeSize
|
|
253
|
-
@Environment(\.horizontalSizeClass) var sizeClass
|
|
254
|
-
@Environment(\.isSearching) var isSearching
|
|
255
|
-
@Environment(\.openURL) var openURL
|
|
256
|
-
@Environment(\.modelContext) var modelContext
|
|
257
|
-
```
|
|
298
|
+
`\.dismiss`, `\.colorScheme`, `\.dynamicTypeSize`, `\.horizontalSizeClass`,
|
|
299
|
+
`\.isSearching`, `\.openURL`, `\.modelContext`.
|
|
258
300
|
|
|
259
|
-
##
|
|
301
|
+
## 6. Loading Data with .task
|
|
260
302
|
|
|
261
|
-
|
|
303
|
+
Start async work with `.task`. SwiftUI cancels it when the view goes away.
|
|
262
304
|
|
|
263
305
|
```swift
|
|
264
|
-
struct
|
|
265
|
-
@State var store =
|
|
306
|
+
struct ForecastView: View {
|
|
307
|
+
@State private var store = ForecastStore()
|
|
266
308
|
|
|
267
309
|
var body: some View {
|
|
268
|
-
List(store.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
.task { await store.load() }
|
|
272
|
-
.refreshable { await store.refresh() }
|
|
310
|
+
List(store.days) { DayRow(day: $0) }
|
|
311
|
+
.task { await store.load() }
|
|
312
|
+
.refreshable { await store.reload() }
|
|
273
313
|
}
|
|
274
314
|
}
|
|
275
315
|
```
|
|
276
316
|
|
|
277
|
-
|
|
317
|
+
`.task(id:)` cancels and restarts whenever the id changes:
|
|
278
318
|
|
|
279
319
|
```swift
|
|
280
|
-
.task(id:
|
|
281
|
-
guard !
|
|
282
|
-
await
|
|
320
|
+
.task(id: query) {
|
|
321
|
+
guard !query.isEmpty else { return }
|
|
322
|
+
await lookUp(query)
|
|
283
323
|
}
|
|
284
324
|
```
|
|
285
325
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
-
|
|
307
|
-
|
|
308
|
-
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
326
|
+
- Do not start an unmanaged `Task` in `onAppear` unless you store it and cancel
|
|
327
|
+
it yourself.
|
|
328
|
+
- A `Task {}` inside a synchronous action, such as a button handler, is fine:
|
|
329
|
+
update state right away, then await the slow part.
|
|
330
|
+
|
|
331
|
+
## 7. iOS 26 Additions
|
|
332
|
+
|
|
333
|
+
| API | What it does |
|
|
334
|
+
|---|---|
|
|
335
|
+
| `.scrollEdgeEffectStyle(.soft, for: .top)` | Soft fade where content meets a scroll edge |
|
|
336
|
+
| `.backgroundExtensionEffect()` | Extends a mirrored, blurred copy of the view under the safe areas |
|
|
337
|
+
| `@Animatable` macro | Synthesises `animatableData` for a type; see `swiftui-animation` |
|
|
338
|
+
| `TextEditor(text:)` with an `AttributedString` binding | Rich text editing |
|
|
339
|
+
|
|
340
|
+
## 8. Performance
|
|
341
|
+
|
|
342
|
+
- Large collections use `LazyVStack`, `LazyHStack`, `LazyVGrid` or
|
|
343
|
+
`LazyHGrid`. A plain stack creates every child immediately.
|
|
344
|
+
- `List` and `ForEach` data must be `Identifiable` with stable ids. Never use
|
|
345
|
+
array positions as ids.
|
|
346
|
+
- Filter and sort in the model or in a computed property, not inline in
|
|
347
|
+
`body`.
|
|
348
|
+
- A complex view that re-evaluates without need can conform to `Equatable` so
|
|
349
|
+
SwiftUI can skip it when inputs match.
|
|
350
|
+
|
|
351
|
+
## 9. Following the HIG
|
|
352
|
+
|
|
353
|
+
- Semantic colors (`Color.primary`, `.secondary`,
|
|
354
|
+
`Color(uiColor: .systemBackground)`) adapt to light and dark mode.
|
|
355
|
+
- Text styles (`.title`, `.headline`, `.body`, `.caption`) scale with Dynamic
|
|
356
|
+
Type.
|
|
357
|
+
- Empty and error states use `ContentUnavailableView`.
|
|
358
|
+
- Leave `spacing:` off stacks. The default `nil` picks platform spacing; pass a
|
|
359
|
+
number only for a deliberate choice.
|
|
360
|
+
- Adapt layout to `horizontalSizeClass`.
|
|
361
|
+
- Give controls an `.accessibilityLabel`, and switch stack axis when
|
|
362
|
+
`dynamicTypeSize.isAccessibilitySize` is true.
|
|
363
|
+
|
|
364
|
+
Details, token scales and examples:
|
|
365
|
+
[references/design-polish.md](references/design-polish.md).
|
|
366
|
+
|
|
367
|
+
## 10. Writing Tools (iOS 18+)
|
|
368
|
+
|
|
369
|
+
`.writingToolsBehavior(_:)` decides how Apple Intelligence Writing Tools appear
|
|
370
|
+
on a text view.
|
|
371
|
+
|
|
372
|
+
| Value | Experience | Good for |
|
|
373
|
+
|---|---|---|
|
|
374
|
+
| `.automatic` | System decides from context (default) | Most views |
|
|
375
|
+
| `.complete` | Full inline proofread, rewrite and transform | Notes, mail, documents |
|
|
376
|
+
| `.limited` | Reduced experience in an overlay panel | Code editors, validated fields |
|
|
377
|
+
| `.disabled` | Writing Tools not offered | Passwords, search fields |
|
|
325
378
|
|
|
326
379
|
```swift
|
|
327
|
-
TextEditor(text: $
|
|
380
|
+
TextEditor(text: $journalEntry)
|
|
328
381
|
.writingToolsBehavior(.complete)
|
|
329
|
-
|
|
382
|
+
|
|
383
|
+
TextField("Find", text: $filter)
|
|
330
384
|
.writingToolsBehavior(.disabled)
|
|
331
385
|
```
|
|
332
386
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
387
|
+
In UIKit, `UITextView.isWritingToolsActive` is true while a session is running.
|
|
388
|
+
Hold off validation and undo grouping until it turns false.
|
|
389
|
+
|
|
390
|
+
Apple docs:
|
|
391
|
+
[WritingToolsBehavior](https://developer.apple.com/documentation/swiftui/writingtoolsbehavior),
|
|
392
|
+
[writingToolsBehavior(_:)](https://developer.apple.com/documentation/swiftui/view/writingtoolsbehavior(_:)).
|
|
393
|
+
|
|
394
|
+
## 11. Common Mistakes
|
|
395
|
+
|
|
396
|
+
| Mistake | Instead |
|
|
397
|
+
|---|---|
|
|
398
|
+
| Creating an object in `@ObservedObject` | `@State` (Observation) or `@StateObject` (legacy) |
|
|
399
|
+
| Expensive work in `body` | Model or computed property |
|
|
400
|
+
| `Task` started in `onAppear` | `.task`; an unstored `Task` is never cancelled |
|
|
401
|
+
| Array indices as `ForEach` ids | Stable `Identifiable` ids; indices break diffing |
|
|
402
|
+
| `$store.x` without `@Bindable` | Declare `@Bindable` for `$` on an `@Observable` |
|
|
403
|
+
| `@State` used for shared data | `@State` is view-local; share through an `@Observable` |
|
|
404
|
+
| One very long `body` | Extract subviews |
|
|
405
|
+
| `NavigationView` | `NavigationStack` |
|
|
406
|
+
| `foregroundColor(_:)` | `foregroundStyle(_:)` for semantic styles |
|
|
407
|
+
| Large closures inline in `body` | Private methods |
|
|
408
|
+
| `.sheet(isPresented:)` for a model value | `.sheet(item:)` |
|
|
409
|
+
| `AnyView` to mix types | `@ViewBuilder`, `Group` or generics; `AnyView` defeats identity and diffing ([migration notes](references/deprecated-migration.md#10-anyview-to-viewbuilder-and-concrete-types)) |
|
|
410
|
+
| `@AppStorage` inside an `@Observable` class | See below |
|
|
411
|
+
| A `spacing:` value on every stack | Omit unless intentional |
|
|
412
|
+
|
|
413
|
+
`@AppStorage` is a `DynamicProperty`. It only drives updates when it lives in a
|
|
414
|
+
`View`; inside an `@Observable` class, observation never sees its changes. Keep
|
|
415
|
+
it in the view, or use a stored property backed by `UserDefaults`:
|
|
352
416
|
|
|
353
417
|
```swift
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Right -- UserDefaults read/write with a normal stored property
|
|
360
|
-
@MainActor @Observable final class Settings {
|
|
361
|
-
var theme: String {
|
|
362
|
-
didSet { UserDefaults.standard.set(theme, forKey: "theme") }
|
|
418
|
+
@MainActor @Observable
|
|
419
|
+
final class ReaderPreferences {
|
|
420
|
+
var fontName: String {
|
|
421
|
+
didSet { UserDefaults.standard.set(fontName, forKey: "reader.font") }
|
|
363
422
|
}
|
|
364
423
|
|
|
365
424
|
init() {
|
|
366
|
-
|
|
425
|
+
fontName = UserDefaults.standard.string(forKey: "reader.font") ?? "Georgia"
|
|
367
426
|
}
|
|
368
427
|
}
|
|
369
428
|
```
|
|
370
429
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
- [ ]
|
|
376
|
-
- [ ]
|
|
377
|
-
- [ ]
|
|
378
|
-
- [ ]
|
|
379
|
-
- [ ]
|
|
380
|
-
- [ ]
|
|
381
|
-
- [ ]
|
|
382
|
-
- [ ]
|
|
383
|
-
- [ ]
|
|
384
|
-
- [ ]
|
|
385
|
-
- [ ]
|
|
386
|
-
- [ ]
|
|
387
|
-
- [ ]
|
|
388
|
-
- [ ]
|
|
389
|
-
- [ ]
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
-
|
|
398
|
-
|
|
399
|
-
|
|
430
|
+
## 12. Review Checklist
|
|
431
|
+
|
|
432
|
+
- [ ] Shared models use `@Observable` (iOS 17+), not `ObservableObject`
|
|
433
|
+
- [ ] `@State` owns objects; children receive them as `let` or `@Bindable`
|
|
434
|
+
- [ ] `NavigationStack` instead of `NavigationView`
|
|
435
|
+
- [ ] Async loading goes through `.task`
|
|
436
|
+
- [ ] Large collections use lazy stacks
|
|
437
|
+
- [ ] `ForEach` ids are stable `Identifiable` ids, not indices
|
|
438
|
+
- [ ] Views are split into focused subviews
|
|
439
|
+
- [ ] `body` does no heavy work
|
|
440
|
+
- [ ] Deeply shared state travels through the environment
|
|
441
|
+
- [ ] `foregroundStyle(_:)` where a semantic style fits better than a fixed color
|
|
442
|
+
- [ ] Repeated styling lives in a custom `ViewModifier`
|
|
443
|
+
- [ ] `.sheet(item:)` preferred to `.sheet(isPresented:)`
|
|
444
|
+
- [ ] Sheets own their actions and call `dismiss()` themselves
|
|
445
|
+
- [ ] MV followed; no view model without a reason
|
|
446
|
+
- [ ] `@Observable` view-facing classes are `@MainActor`
|
|
447
|
+
- [ ] Model types that cross concurrency boundaries are `Sendable`
|
|
448
|
+
- [ ] Stack `spacing:` omitted unless a specific value is needed
|
|
449
|
+
|
|
450
|
+
## 13. References
|
|
451
|
+
|
|
452
|
+
- [references/architecture-patterns.md](references/architecture-patterns.md):
|
|
453
|
+
the case for MV, when a view model is justified, environment versus init
|
|
454
|
+
injection, testing, app shell wiring, dependency modifier, `ModelContainer`,
|
|
455
|
+
enum sheets, deep links, closure-based clients.
|
|
456
|
+
- [references/design-polish.md](references/design-polish.md): HIG layout,
|
|
457
|
+
typography and color, theming, haptics and Core Haptics, matched transitions,
|
|
458
|
+
loading placeholders, form focus.
|
|
459
|
+
- [references/deprecated-migration.md](references/deprecated-migration.md):
|
|
460
|
+
old API to current API, with the release each replacement needs.
|
|
461
|
+
- [references/platform-and-sharing.md](references/platform-and-sharing.md):
|
|
462
|
+
`Transferable`, share and drag and drop, pasteboard, media viewer, top bar
|
|
463
|
+
overlays, title menus, input bars, menu bar commands, macOS settings.
|