@mmerterden/multi-agent-pipeline 20.6.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 +27 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +328 -329
- package/package.json +2 -2
- package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
- package/pipeline/scripts/_notices.mjs +12 -1
- package/pipeline/scripts/gen-skills-index.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +106 -106
- package/pipeline/skills/shared/README.md +71 -71
- package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
- 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/android-architecture/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
- 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/backlog/BACKLOG.md +1 -1
- package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
- 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/ci-cd-pipelines/SKILL.md +1 -0
- 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/compose-components/SKILL.md +2 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
- package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
- 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/council/SKILL.md +1 -0
- 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/css-modern/SKILL.md +1 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
- 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/evidence-github/SKILL.md +2 -0
- package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
- package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
- 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/html-semantic/SKILL.md +1 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
- 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-coding-standard/SKILL.md +1 -0
- 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-module-structure/SKILL.md +1 -0
- 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-security/SKILL.md +2 -0
- 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/localization-reuse-map/SKILL.md +91 -283
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
- 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/nextjs-app-router/SKILL.md +1 -0
- 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/play-store-review/SKILL.md +2 -0
- 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/python-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
- 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/rest-api-design/SKILL.md +1 -0
- package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
- package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
- package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
- 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/signal-community/SKILL.md +2 -0
- package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
- package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
- package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
- package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
- package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
- package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
- package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
- 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/tailwind-css/SKILL.md +1 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
- 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/typescript-patterns/SKILL.md +1 -0
- 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/vue-composition/SKILL.md +1 -0
- 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/web-accessibility/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
- 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,477 +1,314 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: swiftui-performance
|
|
3
|
-
description: "
|
|
3
|
+
description: "SwiftUI performance: slow rendering, janky scrolling, high CPU/memory, excess updates, layout thrash, costly body, identity churn, lazy loading, Observation dependencies, Instruments profiling. Use when auditing, diagnosing or improving SwiftUI performance from code or a trace. Not for report-style audits (swiftui-performance-audit)."
|
|
4
4
|
metadata:
|
|
5
|
-
source:
|
|
5
|
+
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# SwiftUI Performance
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## Contents
|
|
13
|
-
|
|
14
|
-
- [Workflow Decision Tree](#workflow-decision-tree)
|
|
15
|
-
- [1. Code-First Review](#1-code-first-review)
|
|
16
|
-
- [2. Guide the User to Profile](#2-guide-the-user-to-profile)
|
|
17
|
-
- [3. Analyze and Diagnose](#3-analyze-and-diagnose)
|
|
18
|
-
- [4. Remediate](#4-remediate)
|
|
19
|
-
- [Common Code Smells (and Fixes)](#common-code-smells-and-fixes)
|
|
20
|
-
- [5. Verify](#5-verify)
|
|
21
|
-
- [Outputs](#outputs)
|
|
22
|
-
- [Instruments Profiling](#instruments-profiling)
|
|
23
|
-
- [Identity and Lifetime](#identity-and-lifetime)
|
|
24
|
-
- [Lazy Loading Patterns](#lazy-loading-patterns)
|
|
25
|
-
- [State and Observation Optimization](#state-and-observation-optimization)
|
|
26
|
-
- [Common Mistakes](#common-mistakes)
|
|
27
|
-
- [Review Checklist](#review-checklist)
|
|
28
|
-
- [References](#references)
|
|
29
|
-
|
|
30
|
-
## Workflow Decision Tree
|
|
31
|
-
|
|
32
|
-
- If the user provides code, start with "Code-First Review."
|
|
33
|
-
- If the user only describes symptoms, ask for minimal code/context, then do "Code-First Review."
|
|
34
|
-
- If code review is inconclusive, go to "Guide the User to Profile" and ask for a trace or screenshots.
|
|
35
|
-
|
|
36
|
-
## 1. Code-First Review
|
|
37
|
-
|
|
38
|
-
Collect:
|
|
39
|
-
- Target view/feature code.
|
|
40
|
-
- Data flow: state, environment, observable models.
|
|
41
|
-
- Symptoms and reproduction steps.
|
|
42
|
-
|
|
43
|
-
Focus on:
|
|
44
|
-
- View invalidation storms from broad state changes.
|
|
45
|
-
- Unstable identity in lists (`id` churn, `UUID()` per render).
|
|
46
|
-
- Top-level conditional view swapping (`if/else` returning different root branches).
|
|
47
|
-
- Heavy work in `body` (formatting, sorting, image decoding).
|
|
48
|
-
- Layout thrash (deep stacks, `GeometryReader`, preference chains).
|
|
49
|
-
- Large images without downsampling or resizing.
|
|
50
|
-
- Over-animated hierarchies (implicit animations on large trees).
|
|
51
|
-
|
|
52
|
-
Provide:
|
|
53
|
-
- Likely root causes with code references.
|
|
54
|
-
- Suggested fixes and refactors.
|
|
55
|
-
- If needed, a minimal repro or instrumentation suggestion.
|
|
56
|
-
|
|
57
|
-
## 2. Guide the User to Profile
|
|
58
|
-
|
|
59
|
-
Explain how to collect data with Instruments:
|
|
60
|
-
- Use the SwiftUI template in Instruments.
|
|
61
|
-
- Profile a **Release build** on a real device when possible.
|
|
62
|
-
- Reproduce the exact interaction (scroll, navigation, animation).
|
|
63
|
-
- Capture SwiftUI timeline and Time Profiler.
|
|
64
|
-
- Export or screenshot the relevant lanes and the call tree.
|
|
65
|
-
|
|
66
|
-
Ask for:
|
|
67
|
-
- Trace export or screenshots of SwiftUI lanes + Time Profiler call tree.
|
|
68
|
-
- Device/OS/build configuration.
|
|
69
|
-
|
|
70
|
-
## 3. Analyze and Diagnose
|
|
71
|
-
|
|
72
|
-
Prioritize likely SwiftUI culprits:
|
|
73
|
-
- View invalidation storms from broad state changes.
|
|
74
|
-
- Unstable identity in lists (`id` churn, `UUID()` per render).
|
|
75
|
-
- Top-level conditional view swapping (`if/else` returning different root branches).
|
|
76
|
-
- Heavy work in `body` (formatting, sorting, image decoding).
|
|
77
|
-
- Layout thrash (deep stacks, `GeometryReader`, preference chains).
|
|
78
|
-
- Large images without downsampling or resizing.
|
|
79
|
-
- Over-animated hierarchies (implicit animations on large trees).
|
|
80
|
-
|
|
81
|
-
Summarize findings with evidence from traces/logs.
|
|
82
|
-
|
|
83
|
-
## 4. Remediate
|
|
84
|
-
|
|
85
|
-
Apply targeted fixes:
|
|
86
|
-
- Narrow state scope (`@State`/`@Observable` closer to leaf views).
|
|
87
|
-
- Stabilize identities for `ForEach` and lists.
|
|
88
|
-
- Move heavy work out of `body` (precompute, cache, `@State`).
|
|
89
|
-
- Use `equatable()` or value wrappers for expensive subtrees.
|
|
90
|
-
- Downsample images before rendering.
|
|
91
|
-
- Reduce layout complexity or use fixed sizing where possible.
|
|
92
|
-
|
|
93
|
-
## Common Code Smells (and Fixes)
|
|
94
|
-
|
|
95
|
-
Look for these patterns during code review.
|
|
96
|
-
|
|
97
|
-
### Expensive formatters in `body`
|
|
10
|
+
Baseline: iOS 26 and Xcode 26. Observation needs iOS 17; APIs that need a
|
|
11
|
+
later release say so.
|
|
98
12
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
let measure = MeasurementFormatter() // slow allocation
|
|
103
|
-
Text(measure.string(from: .init(value: meters, unit: .meters)))
|
|
104
|
-
}
|
|
105
|
-
```
|
|
13
|
+
A symptom-first audit whose deliverable is a written report belongs to
|
|
14
|
+
`swiftui-performance-audit`. General LLDB and Instruments technique is in
|
|
15
|
+
`debugging-instruments`.
|
|
106
16
|
|
|
107
|
-
|
|
17
|
+
## Where to Start
|
|
108
18
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
```
|
|
19
|
+
| You have | Do this |
|
|
20
|
+
|----------|---------|
|
|
21
|
+
| The code | Start with the code review below |
|
|
22
|
+
| Only a description of the symptom | Ask for the smallest amount of code and context that shows it, then review the code |
|
|
23
|
+
| A code review that found nothing convincing | Walk the user through a profiling session and ask for the trace or screenshots |
|
|
116
24
|
|
|
117
|
-
|
|
25
|
+
## 1. Review the Code
|
|
118
26
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
items.filter { $0.isEnabled } // runs on every body eval
|
|
122
|
-
}
|
|
123
|
-
```
|
|
27
|
+
Collect three things: the view or feature code, how data flows into it (state,
|
|
28
|
+
environment, observable models), and the symptom with steps to reproduce.
|
|
124
29
|
|
|
125
|
-
|
|
30
|
+
Look for these, most frequent first:
|
|
126
31
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
32
|
+
- A broad state change that invalidates far more views than it needs to.
|
|
33
|
+
- List identity that is not stable: ids that change, a `UUID()` made during
|
|
34
|
+
render.
|
|
35
|
+
- A root `if`/`else` that swaps between two different view trees.
|
|
36
|
+
- Expensive work inside `body`: formatting, sorting, image decoding.
|
|
37
|
+
- Layout thrash from deep stacks, `GeometryReader` or chains of preferences.
|
|
38
|
+
- Large images shown without downsampling.
|
|
39
|
+
- Implicit animations applied to large subtrees.
|
|
131
40
|
|
|
132
|
-
|
|
41
|
+
Report the likely causes with references to the code, the fixes or refactors,
|
|
42
|
+
and where useful a small repro or an instrumentation idea.
|
|
133
43
|
|
|
134
|
-
|
|
135
|
-
// DON'T: sorts or filters on every body evaluation
|
|
136
|
-
ForEach(items.sorted(by: sortRule)) { item in Row(item) }
|
|
137
|
-
ForEach(items.filter { $0.isEnabled }) { item in Row(item) }
|
|
138
|
-
```
|
|
44
|
+
## 2. Profile When Code Is Not Enough
|
|
139
45
|
|
|
140
|
-
|
|
46
|
+
Ask the user to:
|
|
141
47
|
|
|
142
|
-
|
|
48
|
+
1. Profile with the SwiftUI template in Instruments (Cmd+I).
|
|
49
|
+
2. Use a Release build on a real device whenever possible.
|
|
50
|
+
3. Repeat the exact interaction: the scroll, the push, the animation.
|
|
51
|
+
4. Capture the SwiftUI lanes and Time Profiler, then export the trace or take
|
|
52
|
+
screenshots of the lanes and the call tree.
|
|
53
|
+
5. Send those along with the device, OS version and build configuration.
|
|
143
54
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
Row(item)
|
|
147
|
-
}
|
|
148
|
-
```
|
|
55
|
+
The full lane-by-lane workflow is in
|
|
56
|
+
[the SwiftUI Instrument guide](references/optimizing-swiftui-performance-instruments.md).
|
|
149
57
|
|
|
150
|
-
|
|
58
|
+
## 3. Diagnose
|
|
151
59
|
|
|
152
|
-
|
|
60
|
+
Use the same list as the code review (broad invalidation, unstable identity,
|
|
61
|
+
swapped roots, heavy bodies, layout thrash, oversized images, too much
|
|
62
|
+
animation) and tie every conclusion to evidence from the trace or logs.
|
|
153
63
|
|
|
154
|
-
|
|
155
|
-
var content: some View {
|
|
156
|
-
if isEditing {
|
|
157
|
-
editingView
|
|
158
|
-
} else {
|
|
159
|
-
readOnlyView
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
```
|
|
64
|
+
## 4. Fix
|
|
163
65
|
|
|
164
|
-
|
|
66
|
+
- Move state closer to the leaves that read it (`@State`, `@Observable`).
|
|
67
|
+
- Give `ForEach` and `List` identities that do not change.
|
|
68
|
+
- Take heavy work out of `body`: compute once, cache, keep results in `@State`
|
|
69
|
+
or the model.
|
|
70
|
+
- Put `equatable()` or a small value wrapper around expensive subtrees.
|
|
71
|
+
- Downsample images before they reach the view.
|
|
72
|
+
- Simplify layout, or use fixed sizes where the design allows.
|
|
165
73
|
|
|
166
|
-
###
|
|
74
|
+
### Smells and their fixes
|
|
167
75
|
|
|
168
76
|
```swift
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
### Broad dependencies in observable models
|
|
175
|
-
|
|
176
|
-
```swift
|
|
177
|
-
@Observable class Model {
|
|
178
|
-
var items: [Item] = []
|
|
77
|
+
struct FuelRow: View {
|
|
78
|
+
let litres: Double
|
|
79
|
+
var body: some View {
|
|
80
|
+
Text(FuelFormat.shared.string(from: litres as NSNumber) ?? "")
|
|
81
|
+
}
|
|
179
82
|
}
|
|
180
83
|
|
|
181
|
-
|
|
182
|
-
|
|
84
|
+
enum FuelFormat {
|
|
85
|
+
static let shared: NumberFormatter = {
|
|
86
|
+
let formatter = NumberFormatter()
|
|
87
|
+
formatter.maximumFractionDigits = 1
|
|
88
|
+
return formatter
|
|
89
|
+
}()
|
|
183
90
|
}
|
|
184
91
|
```
|
|
185
92
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
## 5. Verify
|
|
189
|
-
|
|
190
|
-
Ask the user to re-run the same capture and compare with baseline metrics.
|
|
191
|
-
Summarize the delta (CPU, frame drops, memory peak) if provided.
|
|
93
|
+
Building a `NumberFormatter` or `MeasurementFormatter` in `body` costs time on
|
|
94
|
+
every evaluation. Keep one cached instance as above, or use a `FormatStyle`.
|
|
192
95
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
- Proposed fixes with estimated effort.
|
|
199
|
-
|
|
200
|
-
## Instruments Profiling
|
|
201
|
-
|
|
202
|
-
Use the **SwiftUI instrument template** in Xcode (Cmd+I to profile). Key instruments: SwiftUI View Body (body evaluation counts), SwiftUI View Properties (state change tracking), Time Profiler, and Hangs.
|
|
96
|
+
```swift
|
|
97
|
+
struct TripList: View {
|
|
98
|
+
let trips: [Trip]
|
|
99
|
+
let query: String
|
|
100
|
+
@State private var visible: [Trip] = []
|
|
203
101
|
|
|
204
|
-
|
|
102
|
+
var body: some View {
|
|
103
|
+
List(visible) { trip in
|
|
104
|
+
TripRow(trip: trip)
|
|
105
|
+
}
|
|
106
|
+
.onChange(of: trips, initial: true) { refresh() }
|
|
107
|
+
.onChange(of: query) { refresh() }
|
|
108
|
+
}
|
|
205
109
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
Text("Count: \(count)")
|
|
110
|
+
private func refresh() {
|
|
111
|
+
visible = trips
|
|
112
|
+
.filter { query.isEmpty || $0.name.localizedStandardContains(query) }
|
|
113
|
+
.sorted { $0.departure < $1.departure }
|
|
114
|
+
}
|
|
212
115
|
}
|
|
213
116
|
```
|
|
214
117
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
### Structural Identity vs Explicit Identity
|
|
118
|
+
A computed property that filters, or `ForEach(items.sorted(by:))` and
|
|
119
|
+
`ForEach(items.filter { ... })`, redo the work on every evaluation. Keep the
|
|
120
|
+
result in `@State` (or the model) and refresh it only when an input changes;
|
|
121
|
+
the stored array also keeps identities stable.
|
|
220
122
|
|
|
221
|
-
|
|
123
|
+
| Smell | Fix |
|
|
124
|
+
|-------|-----|
|
|
125
|
+
| `ForEach(tags, id: \.self)` over values that can repeat or change | Use a stable identifier |
|
|
126
|
+
| Root `if isEditing { EditorView() } else { ReaderView() }` | Keep one base view and put the condition inside sections, row content or modifiers (`toolbar`, `overlay`, `disabled`) |
|
|
127
|
+
| `Image(uiImage: UIImage(data: data)!)` in `body` | Decode and downsample off the main thread, store the resulting image |
|
|
128
|
+
| Every row reads `model.favorites.contains(item)` on one broad `@Observable` | Give rows per-item state or a narrower model so one change does not reach every row |
|
|
222
129
|
|
|
223
|
-
|
|
224
|
-
- **Explicit identity**: you assign with `.id(_:)` modifier or `ForEach(items, id: \.stableID)`.
|
|
225
|
-
|
|
226
|
-
```swift
|
|
227
|
-
// Structural identity: SwiftUI knows these are different views by position
|
|
228
|
-
VStack {
|
|
229
|
-
Text("First") // position 0
|
|
230
|
-
Text("Second") // position 1
|
|
231
|
-
}
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### How Identity Tracks View Lifetime
|
|
130
|
+
## 5. Verify
|
|
235
131
|
|
|
236
|
-
|
|
132
|
+
Ask the user to repeat the same capture and compare it with the first one:
|
|
133
|
+
CPU, dropped frames and peak memory.
|
|
237
134
|
|
|
238
|
-
|
|
239
|
-
-
|
|
240
|
-
-
|
|
241
|
-
-
|
|
135
|
+
Deliver:
|
|
136
|
+
- a short before and after metrics table,
|
|
137
|
+
- the main issues ordered by impact,
|
|
138
|
+
- the fixes with a rough effort estimate.
|
|
242
139
|
|
|
243
|
-
|
|
140
|
+
## Instruments
|
|
244
141
|
|
|
245
|
-
|
|
142
|
+
Profile with Cmd+I and the SwiftUI template. In Instruments 26 the SwiftUI
|
|
143
|
+
instrument shows lanes such as Update Groups, Long View Body Updates and the
|
|
144
|
+
Cause and Effect Graph. Older Instruments versions offered separate SwiftUI
|
|
145
|
+
View Body (how often bodies ran) and SwiftUI View Properties (which properties
|
|
146
|
+
changed) instruments. Pair either with Time Profiler and Hangs.
|
|
246
147
|
|
|
247
|
-
|
|
148
|
+
To see why a particular view updated while debugging:
|
|
248
149
|
|
|
249
150
|
```swift
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// DO: use @ViewBuilder to preserve structural identity
|
|
260
|
-
@ViewBuilder
|
|
261
|
-
func makeView(for item: Item) -> some View {
|
|
262
|
-
if item.isPremium {
|
|
263
|
-
PremiumRow(item: item)
|
|
264
|
-
} else {
|
|
265
|
-
StandardRow(item: item)
|
|
151
|
+
struct ScoreBadge: View {
|
|
152
|
+
let score: Int
|
|
153
|
+
var body: some View {
|
|
154
|
+
#if DEBUG
|
|
155
|
+
let _ = Self._printChanges()
|
|
156
|
+
#endif
|
|
157
|
+
Text(score, format: .number)
|
|
266
158
|
}
|
|
267
159
|
}
|
|
268
160
|
```
|
|
269
161
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
### Ternary Modifiers Preserve Structural Identity
|
|
273
|
-
|
|
274
|
-
`if`/`else` in a view builder creates `_ConditionalContent` - two separate view branches with distinct identities. When the condition changes, SwiftUI destroys one branch and creates the other, resetting all `@State`.
|
|
275
|
-
|
|
276
|
-
For toggling **modifiers** on the same view, use a ternary expression instead:
|
|
277
|
-
|
|
278
|
-
```swift
|
|
279
|
-
// DON'T: if/else creates two separate Text views with different identities
|
|
280
|
-
if isHighlighted {
|
|
281
|
-
Text(title).foregroundStyle(.yellow)
|
|
282
|
-
} else {
|
|
283
|
-
Text(title).foregroundStyle(.primary)
|
|
284
|
-
}
|
|
162
|
+
The console then prints lines such as `ScoreBadge: @self, _score changed.`.
|
|
285
163
|
|
|
286
|
-
|
|
287
|
-
Text(title)
|
|
288
|
-
.foregroundStyle(isHighlighted ? .yellow : .primary)
|
|
289
|
-
```
|
|
164
|
+
## Identity and Lifetime
|
|
290
165
|
|
|
291
|
-
|
|
166
|
+
Every view has an identity. SwiftUI uses it to decide how long the view lives,
|
|
167
|
+
which state belongs to it, and what to animate.
|
|
292
168
|
|
|
293
|
-
|
|
169
|
+
- **Structural identity** is the default: the view's type and position in the
|
|
170
|
+
hierarchy.
|
|
171
|
+
- **Explicit identity** comes from `.id(_:)` or from the id in
|
|
172
|
+
`ForEach(items, id: \.code)`.
|
|
294
173
|
|
|
295
|
-
|
|
174
|
+
| Identity changed | Identity kept |
|
|
175
|
+
|------------------|---------------|
|
|
176
|
+
| All `@State` starts over | The view is updated in place |
|
|
177
|
+
| `onAppear` runs again | State survives |
|
|
178
|
+
| Animations can restart and transitions play | Changes animate smoothly |
|
|
296
179
|
|
|
297
|
-
|
|
180
|
+
`AnyView` hides the concrete type, so SwiftUI cannot tell which branch it is
|
|
181
|
+
looking at and falls back to replacing the whole subtree. Return `some View`
|
|
182
|
+
from a `@ViewBuilder` function instead:
|
|
298
183
|
|
|
299
184
|
```swift
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
185
|
+
@ViewBuilder
|
|
186
|
+
func statusView(for status: Shipment.Status) -> some View {
|
|
187
|
+
if status == .delivered {
|
|
188
|
+
Label("Delivered", systemImage: "checkmark.circle")
|
|
189
|
+
} else {
|
|
190
|
+
ProgressView(value: status.progress)
|
|
307
191
|
}
|
|
308
192
|
}
|
|
309
|
-
|
|
310
|
-
// DO: use a stable identifier
|
|
311
|
-
ForEach(items) { item in
|
|
312
|
-
Row(item: item)
|
|
313
|
-
.id(item.stableID) // identity only changes when the item actually changes
|
|
314
|
-
}
|
|
315
193
|
```
|
|
316
194
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
### LazyVStack and LazyHStack
|
|
322
|
-
|
|
323
|
-
Lazy stacks only create views for items currently visible on screen. Off-screen items are not evaluated until scrolled into view.
|
|
195
|
+
An `if`/`else` in a view builder produces `_ConditionalContent`: two separate
|
|
196
|
+
identities. Flipping the condition throws one branch away together with its
|
|
197
|
+
`@State`. When only a value changes, use a ternary so there is one view:
|
|
324
198
|
|
|
325
199
|
```swift
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
ForEach(items) { item in
|
|
329
|
-
ItemRow(item: item)
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
200
|
+
Image(systemName: "star.fill")
|
|
201
|
+
.foregroundStyle(isPinned ? .orange : .secondary)
|
|
333
202
|
```
|
|
334
203
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
- `onAppear` fires when the view first enters the visible area.
|
|
338
|
-
- `onDisappear` fires when it leaves, but the view is still alive.
|
|
204
|
+
Rule: `if`/`else` when the view type differs, a ternary when only a property or
|
|
205
|
+
modifier value differs.
|
|
339
206
|
|
|
340
|
-
|
|
207
|
+
Changing the value passed to `.id()` destroys the view and builds a new one.
|
|
208
|
+
`.id(UUID())` on a row in a `LazyVStack` therefore creates a fresh identity on
|
|
209
|
+
every render. Use `.id(item.code)`. Changing the id on purpose is fine when you
|
|
210
|
+
want a reset, for example `.id(activeTab)` to start a scroll view at the top
|
|
211
|
+
again after switching tabs.
|
|
341
212
|
|
|
342
|
-
|
|
213
|
+
## Lazy Containers
|
|
343
214
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
215
|
+
- `LazyVStack` and `LazyHStack` only build views that are on screen; the rest
|
|
216
|
+
are not evaluated until they scroll in.
|
|
217
|
+
- A view built lazily is not destroyed when it scrolls away; it stays in
|
|
218
|
+
memory.
|
|
219
|
+
- `onAppear` runs when a view first enters the visible area; `onDisappear` runs
|
|
220
|
+
when it leaves, while the view itself stays alive.
|
|
221
|
+
- For several columns use `LazyVGrid` or `LazyHGrid`.
|
|
222
|
+
`GridItem(.adaptive(minimum: 150))` fits as many columns as the width
|
|
223
|
+
allows; three `GridItem(.flexible())` give three equal columns.
|
|
347
224
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
PhotoThumbnail(photo: photo)
|
|
352
|
-
}
|
|
353
|
-
}
|
|
225
|
+
```swift
|
|
226
|
+
LazyVGrid(columns: [GridItem(.adaptive(minimum: 150))]) {
|
|
227
|
+
ForEach(albums) { AlbumTile(album: $0) }
|
|
354
228
|
}
|
|
355
|
-
|
|
356
|
-
// Fixed: exact number of equal columns
|
|
357
|
-
let fixedColumns = [
|
|
358
|
-
GridItem(.flexible()),
|
|
359
|
-
GridItem(.flexible()),
|
|
360
|
-
GridItem(.flexible()),
|
|
361
|
-
]
|
|
362
229
|
```
|
|
363
230
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
|
367
|
-
|
|
368
|
-
|
|
|
369
|
-
|
|
|
370
|
-
|
|
|
371
|
-
| Always-visible content | `VStack` (no benefit to lazy) |
|
|
372
|
-
| Scrollable lists | `LazyVStack` inside `ScrollView`, or `List` |
|
|
373
|
-
|
|
374
|
-
**Important:** Do not nest `GeometryReader` inside lazy containers. It forces eager measurement and defeats lazy loading. Use `.onGeometryChange` (iOS 16+) instead.
|
|
231
|
+
| Content | Container |
|
|
232
|
+
|---------|-----------|
|
|
233
|
+
| Fewer than about 50 items | `VStack` or `HStack` is fine |
|
|
234
|
+
| 50 to 100 items | Either; prefer lazy when rows are complex |
|
|
235
|
+
| More than 100 items | `LazyVStack` or `LazyHStack` |
|
|
236
|
+
| Always fully visible | `VStack`; laziness gains nothing |
|
|
237
|
+
| A scrolling list | `LazyVStack` inside `ScrollView`, or `List` |
|
|
375
238
|
|
|
376
|
-
|
|
239
|
+
Do not put `GeometryReader` inside lazy content: it makes SwiftUI measure
|
|
240
|
+
eagerly. Use `.onGeometryChange(for:of:action:)`, which is available back to
|
|
241
|
+
iOS 16 when building with the Xcode 16 SDK or later.
|
|
377
242
|
|
|
378
|
-
|
|
243
|
+
## Observation
|
|
379
244
|
|
|
380
|
-
`@Observable` (
|
|
245
|
+
With `@Observable` (iOS 17), SwiftUI records which properties a `body` reads
|
|
246
|
+
and re-evaluates the view only when one of those changes.
|
|
381
247
|
|
|
382
248
|
```swift
|
|
383
|
-
@Observable
|
|
384
|
-
|
|
249
|
+
@Observable
|
|
250
|
+
final class Profile {
|
|
251
|
+
var displayName = ""
|
|
252
|
+
var biography = ""
|
|
385
253
|
var avatarURL: URL?
|
|
386
|
-
var biography: String = ""
|
|
387
254
|
}
|
|
388
255
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
struct NameLabel: View {
|
|
392
|
-
let profile: UserProfile
|
|
256
|
+
struct ProfileHeader: View {
|
|
257
|
+
let profile: Profile
|
|
393
258
|
var body: some View {
|
|
394
|
-
Text(profile.
|
|
259
|
+
Text(profile.displayName)
|
|
395
260
|
}
|
|
396
261
|
}
|
|
397
262
|
```
|
|
398
263
|
|
|
399
|
-
|
|
264
|
+
`ProfileHeader` ignores changes to `biography` and `avatarURL`. With
|
|
265
|
+
`ObservableObject`, any `@Published` change invalidates every observer.
|
|
400
266
|
|
|
401
|
-
|
|
267
|
+
A single body that reads many properties still has a wide dependency surface.
|
|
268
|
+
Split it into children that each read what they show: a name row, an email row,
|
|
269
|
+
an avatar view, a settings form.
|
|
402
270
|
|
|
403
|
-
|
|
271
|
+
Computed properties derive values without widening the surface:
|
|
404
272
|
|
|
405
273
|
```swift
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
var
|
|
410
|
-
VStack {
|
|
411
|
-
Text(model.name) // tracks name
|
|
412
|
-
Text(model.email) // tracks email
|
|
413
|
-
AsyncImage(url: model.avatar) // tracks avatar
|
|
414
|
-
SettingsForm(model.settings) // tracks settings
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// DO: split into child views so each only tracks what it reads
|
|
420
|
-
struct ProfileView: View {
|
|
421
|
-
let model: ProfileModel
|
|
422
|
-
var body: some View {
|
|
423
|
-
VStack {
|
|
424
|
-
NameRow(model: model) // only tracks name
|
|
425
|
-
EmailRow(model: model) // only tracks email
|
|
426
|
-
AvatarView(model: model) // only tracks avatar
|
|
427
|
-
SettingsForm(model: model) // only tracks settings
|
|
428
|
-
}
|
|
429
|
-
}
|
|
274
|
+
@Observable
|
|
275
|
+
final class Basket {
|
|
276
|
+
var lines: [BasketLine] = []
|
|
277
|
+
var total: Decimal { lines.reduce(0) { $0 + $1.price * Decimal($1.quantity) } }
|
|
430
278
|
}
|
|
431
279
|
```
|
|
432
280
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
Use computed properties on `@Observable` models to derive state without introducing extra stored properties that widen observation scope:
|
|
436
|
-
|
|
437
|
-
```swift
|
|
438
|
-
@Observable class ShoppingCart {
|
|
439
|
-
var items: [CartItem] = []
|
|
440
|
-
|
|
441
|
-
// Views reading `total` only re-render when `items` changes
|
|
442
|
-
var total: Decimal {
|
|
443
|
-
items.reduce(0) { $0 + $1.price * Decimal($1.quantity) }
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
```
|
|
281
|
+
A view that reads `total` updates only when `lines` changes.
|
|
447
282
|
|
|
448
283
|
## Common Mistakes
|
|
449
284
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
285
|
+
| Mistake | Better |
|
|
286
|
+
|---------|--------|
|
|
287
|
+
| Profiling a Debug build (extra checks, no optimisation) | Profile Release on a device |
|
|
288
|
+
| Observing a whole model to show one field | Split the model, or pass a computed value or closure |
|
|
289
|
+
| `GeometryReader` inside `ScrollView` rows | `.onGeometryChange`, or measure outside the lazy content |
|
|
290
|
+
| `DateFormatter()` or `NumberFormatter()` in `body` | A static instance or a `FormatStyle` |
|
|
291
|
+
| Animating state that is not `Equatable`, so every frame redraws | Make it `Equatable`; use `.animation(_:value:)`, or `.animation(_:body:)` (iOS 17) to limit the animation to specific modifiers |
|
|
292
|
+
| A large `List` without identifiers | `Identifiable` data or `id:` |
|
|
293
|
+
| Wrapping a struct in a class to hold it in `@State` | Store the struct in `@State` directly |
|
|
294
|
+
| Reading files, big JSON or images synchronously on the main actor | A `@concurrent` async function (Swift 6.2), a dedicated actor, or `Task.detached` |
|
|
458
295
|
|
|
459
296
|
## Review Checklist
|
|
460
297
|
|
|
461
|
-
- [ ] No
|
|
462
|
-
- [ ] Large lists use `Identifiable`
|
|
463
|
-
- [ ] `@Observable` models expose
|
|
464
|
-
- [ ] Heavy computation
|
|
465
|
-
- [ ] `GeometryReader`
|
|
466
|
-
- [ ] Implicit animations use `.animation(_:value:)`
|
|
467
|
-
- [ ] No synchronous network
|
|
468
|
-
- [ ] Profiling done
|
|
469
|
-
- [ ] `@Observable` view models are `@MainActor
|
|
298
|
+
- [ ] No formatters created in `body`
|
|
299
|
+
- [ ] Large lists use `Identifiable` data or an explicit `id:`
|
|
300
|
+
- [ ] `@Observable` models expose what views actually read, nothing broader
|
|
301
|
+
- [ ] Heavy computation runs off the main actor
|
|
302
|
+
- [ ] No `GeometryReader` inside `LazyVStack`, `LazyHStack` or `List`
|
|
303
|
+
- [ ] Implicit animations use `.animation(_:value:)` or `.animation(_:body:)`
|
|
304
|
+
- [ ] No synchronous network or file I/O on the main thread
|
|
305
|
+
- [ ] Profiling done in Release on a real device
|
|
306
|
+
- [ ] `@Observable` view models are `@MainActor`; types that cross isolation are `Sendable`
|
|
470
307
|
|
|
471
308
|
## References
|
|
472
309
|
|
|
473
|
-
-
|
|
474
|
-
-
|
|
475
|
-
-
|
|
476
|
-
-
|
|
477
|
-
-
|
|
310
|
+
- [Mental model, dependencies and identity](references/demystify-swiftui-performance-wwdc23.md)
|
|
311
|
+
- [Profiling with the SwiftUI Instrument](references/optimizing-swiftui-performance-instruments.md)
|
|
312
|
+
- [Hangs and the main run loop](references/understanding-hangs-in-your-app.md)
|
|
313
|
+
- [Long versus frequent updates](references/understanding-improving-swiftui-performance.md)
|
|
314
|
+
- [Which Apple session or article to consult](references/wwdc-session-sources.md)
|