@devo-bmad-custom/agent-orchestration 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/installer.js +44 -11
- package/package.json +1 -1
- package/src/.agents/skills/audit-website/README.md +20 -20
- package/src/.agents/skills/audit-website/SKILL.md +470 -470
- package/src/.agents/skills/audit-website/agents/openai.yaml +6 -6
- package/src/.agents/skills/audit-website/assets/icon-small.svg +41 -41
- package/src/.agents/skills/audit-website/references/OUTPUT-FORMAT.md +250 -250
- package/src/.agents/skills/clean-code-standards/SKILL.md +104 -104
- package/src/.agents/skills/excalidraw-dark-standard/SKILL.md +281 -281
- package/src/.agents/skills/frontend-responsive-design-standards/SKILL.md +434 -434
- package/src/.agents/skills/java-fundamentals/SKILL.md +116 -116
- package/src/.agents/skills/java-performance/SKILL.md +119 -119
- package/src/.agents/skills/next-best-practices/SKILL.md +153 -153
- package/src/.agents/skills/next-best-practices/async-patterns.md +87 -87
- package/src/.agents/skills/next-best-practices/bundling.md +180 -180
- package/src/.agents/skills/next-best-practices/data-patterns.md +297 -297
- package/src/.agents/skills/next-best-practices/debug-tricks.md +105 -105
- package/src/.agents/skills/next-best-practices/directives.md +73 -73
- package/src/.agents/skills/next-best-practices/error-handling.md +227 -227
- package/src/.agents/skills/next-best-practices/file-conventions.md +140 -140
- package/src/.agents/skills/next-best-practices/font.md +245 -245
- package/src/.agents/skills/next-best-practices/functions.md +108 -108
- package/src/.agents/skills/next-best-practices/hydration-error.md +91 -91
- package/src/.agents/skills/next-best-practices/image.md +173 -173
- package/src/.agents/skills/next-best-practices/metadata.md +301 -301
- package/src/.agents/skills/next-best-practices/parallel-routes.md +287 -287
- package/src/.agents/skills/next-best-practices/route-handlers.md +146 -146
- package/src/.agents/skills/next-best-practices/rsc-boundaries.md +159 -159
- package/src/.agents/skills/next-best-practices/runtime-selection.md +39 -39
- package/src/.agents/skills/next-best-practices/scripts.md +141 -141
- package/src/.agents/skills/next-best-practices/self-hosting.md +371 -371
- package/src/.agents/skills/next-best-practices/suspense-boundaries.md +67 -67
- package/src/.agents/skills/nextjs-app-router-patterns/SKILL.md +537 -537
- package/src/.agents/skills/postgresql-optimization/SKILL.md +404 -404
- package/src/.agents/skills/python-backend/SKILL.md +153 -153
- package/src/.agents/skills/python-fundamentals/SKILL.md +234 -234
- package/src/.agents/skills/python-performance/SKILL.md +404 -404
- package/src/.agents/skills/react-expert/SKILL.md +335 -335
- package/src/.agents/skills/redis-best-practices/SKILL.md +438 -438
- package/src/.agents/skills/security-best-practices/SKILL.md +288 -288
- package/src/.agents/skills/security-review/LICENSE +22 -22
- package/src/.agents/skills/security-review/SKILL.md +312 -312
- package/src/.agents/skills/security-review/infrastructure/docker.md +432 -432
- package/src/.agents/skills/security-review/languages/javascript.md +388 -388
- package/src/.agents/skills/security-review/languages/python.md +363 -363
- package/src/.agents/skills/security-review/references/api-security.md +519 -519
- package/src/.agents/skills/security-review/references/authentication.md +353 -353
- package/src/.agents/skills/security-review/references/authorization.md +372 -372
- package/src/.agents/skills/security-review/references/business-logic.md +443 -443
- package/src/.agents/skills/security-review/references/cryptography.md +329 -329
- package/src/.agents/skills/security-review/references/csrf.md +398 -398
- package/src/.agents/skills/security-review/references/data-protection.md +378 -378
- package/src/.agents/skills/security-review/references/deserialization.md +410 -410
- package/src/.agents/skills/security-review/references/error-handling.md +436 -436
- package/src/.agents/skills/security-review/references/file-security.md +457 -457
- package/src/.agents/skills/security-review/references/injection.md +259 -259
- package/src/.agents/skills/security-review/references/logging.md +433 -433
- package/src/.agents/skills/security-review/references/misconfiguration.md +435 -435
- package/src/.agents/skills/security-review/references/modern-threats.md +475 -475
- package/src/.agents/skills/security-review/references/ssrf.md +415 -415
- package/src/.agents/skills/security-review/references/supply-chain.md +405 -405
- package/src/.agents/skills/security-review/references/xss.md +336 -336
- package/src/.agents/skills/subagent-driven-development/SKILL.md +275 -275
- package/src/.agents/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -26
- package/src/.agents/skills/subagent-driven-development/implementer-prompt.md +113 -113
- package/src/.agents/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -61
- package/src/.agents/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/src/.agents/skills/systematic-debugging/SKILL.md +296 -296
- package/src/.agents/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/src/.agents/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/src/.agents/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/src/.agents/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/src/.agents/skills/systematic-debugging/test-academic.md +14 -14
- package/src/.agents/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/src/.agents/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/src/.agents/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/src/.agents/skills/typescript-best-practices/SKILL.md +373 -373
- package/src/.agents/skills/ui-ux-pro-custom/SKILL.md +348 -348
- package/src/.agents/skills/ui-ux-pro-custom/data/charts.csv +26 -26
- package/src/.agents/skills/ui-ux-pro-custom/data/colors.csv +97 -97
- package/src/.agents/skills/ui-ux-pro-custom/data/icons.csv +101 -101
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/SKILL.md +106 -106
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/accessibility.md +475 -475
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/animation.md +466 -466
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/composition-locals.md +231 -231
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/deprecated-patterns.md +323 -323
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/lists-scrolling.md +400 -400
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/modifiers.md +331 -331
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/navigation.md +416 -416
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/performance.md +446 -446
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/side-effects.md +516 -516
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/foundation-source.md +13327 -13327
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/material3-source.md +19097 -19097
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/navigation-source.md +2947 -2947
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/runtime-source.md +11316 -11316
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/ui-source.md +7896 -7896
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/state-management.md +377 -377
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/styles-experimental.md +470 -470
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/theming-material3.md +349 -349
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/view-composition.md +595 -595
- package/src/.agents/skills/ui-ux-pro-custom/data/landing.csv +31 -31
- package/src/.agents/skills/ui-ux-pro-custom/data/mobile-ui-layout.md +654 -654
- package/src/.agents/skills/ui-ux-pro-custom/data/products.csv +96 -96
- package/src/.agents/skills/ui-ux-pro-custom/data/react-performance.csv +45 -45
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/astro.csv +54 -54
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/flutter.csv +53 -53
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/html-tailwind.csv +56 -56
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/jetpack-compose.csv +53 -53
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nextjs.csv +53 -53
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nuxt-ui.csv +51 -51
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nuxtjs.csv +59 -59
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/react-native.csv +56 -56
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/react.csv +54 -54
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/shadcn.csv +61 -61
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/svelte.csv +54 -54
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/swiftui.csv +51 -51
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/vue.csv +50 -50
- package/src/.agents/skills/ui-ux-pro-custom/data/styles.csv +68 -68
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/alarmkit/SKILL.md +438 -438
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/alarmkit/references/alarmkit-patterns.md +584 -584
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-clips/SKILL.md +436 -436
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-intents/SKILL.md +489 -489
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-intents/references/appintents-advanced.md +1076 -1076
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/SKILL.md +340 -340
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/references/privacy-manifest.md +90 -90
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/references/review-checklists.md +106 -106
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/coreml-conversion.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/coreml-optimization.md +344 -344
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/foundation-models.md +508 -508
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/mlx-swift.md +285 -285
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/authentication/SKILL.md +496 -496
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/authentication/references/keychain-biometric.md +211 -211
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/background-processing/SKILL.md +499 -499
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/background-processing/references/background-task-patterns.md +390 -390
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/callkit-voip/SKILL.md +461 -461
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/callkit-voip/references/callkit-patterns.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/cloudkit-sync/SKILL.md +492 -492
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/cloudkit-sync/references/cloudkit-patterns.md +461 -461
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/codable-patterns/SKILL.md +467 -467
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/contacts-framework/SKILL.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/contacts-framework/references/contacts-patterns.md +409 -409
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-bluetooth/SKILL.md +491 -491
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-bluetooth/references/ble-patterns.md +435 -435
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-motion/SKILL.md +388 -388
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-motion/references/motion-patterns.md +405 -405
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-nfc/SKILL.md +495 -495
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-nfc/references/nfc-patterns.md +420 -420
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/coreml/SKILL.md +459 -459
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/coreml/references/coreml-swift-integration.md +765 -765
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/SKILL.md +422 -422
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/references/instruments-guide.md +387 -387
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/references/lldb-patterns.md +298 -298
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/device-integrity/SKILL.md +477 -477
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/energykit/SKILL.md +460 -460
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/energykit/references/energykit-patterns.md +541 -541
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/eventkit-calendar/SKILL.md +483 -483
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/eventkit-calendar/references/eventkit-patterns.md +326 -326
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/healthkit/SKILL.md +498 -498
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/healthkit/references/healthkit-patterns.md +602 -602
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/homekit-matter/SKILL.md +496 -496
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/homekit-matter/references/matter-commissioning.md +455 -455
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-accessibility/SKILL.md +301 -301
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-accessibility/references/a11y-patterns.md +140 -140
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/SKILL.md +418 -418
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/references/formatstyle-locale.md +627 -627
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/references/string-catalogs.md +462 -462
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/SKILL.md +441 -441
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/background-websocket.md +862 -862
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/lightweight-clients.md +93 -93
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/network-framework.md +563 -563
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/urlsession-patterns.md +1116 -1116
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/SKILL.md +496 -496
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/app-review-guidelines.md +174 -174
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/cryptokit-advanced.md +296 -296
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/file-storage-patterns.md +354 -354
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/privacy-manifest.md +117 -117
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/live-activities/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/live-activities/references/live-activity-patterns.md +868 -868
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/SKILL.md +485 -485
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/references/corelocation-patterns.md +730 -730
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/references/mapkit-patterns.md +748 -748
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/metrickit-diagnostics/SKILL.md +479 -479
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/musickit-audio/SKILL.md +395 -395
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/musickit-audio/references/musickit-patterns.md +363 -363
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/natural-language/SKILL.md +412 -412
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/natural-language/references/translation-patterns.md +311 -311
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/passkit-wallet/SKILL.md +398 -398
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/passkit-wallet/references/wallet-passes.md +254 -254
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/SKILL.md +387 -387
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/references/paperkit-integration.md +376 -376
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/references/pencilkit-patterns.md +302 -302
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/permissionkit/SKILL.md +446 -446
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/permissionkit/references/permissionkit-patterns.md +435 -435
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/av-playback.md +701 -701
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/camera-capture.md +774 -774
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/image-loading-caching.md +869 -869
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/photospicker-patterns.md +597 -597
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/references/notification-patterns.md +677 -677
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/references/rich-notifications.md +745 -745
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/realitykit-ar/SKILL.md +479 -479
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/realitykit-ar/references/realitykit-patterns.md +480 -480
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/shareplay-activities/SKILL.md +483 -483
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/shareplay-activities/references/shareplay-patterns.md +544 -544
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/speech-recognition/SKILL.md +485 -485
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/SKILL.md +478 -478
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/references/app-review-guidelines.md +58 -58
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/references/storekit-advanced.md +755 -755
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-charts/SKILL.md +487 -487
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-charts/references/charts-patterns.md +895 -895
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/SKILL.md +408 -408
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/approachable-concurrency.md +80 -80
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/swift-6-2-concurrency.md +233 -233
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/swiftui-concurrency.md +187 -187
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/synchronization-primitives.md +341 -341
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-language/SKILL.md +498 -498
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-language/references/swift-patterns-extended.md +505 -505
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-testing/SKILL.md +467 -467
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-testing/references/testing-patterns.md +504 -504
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/SKILL.md +334 -334
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/core-data-coexistence.md +504 -504
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/swiftdata-advanced.md +975 -975
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/swiftdata-queries.md +675 -675
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/SKILL.md +481 -481
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/references/animation-advanced.md +804 -804
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/references/core-animation-bridge.md +553 -553
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-gestures/SKILL.md +450 -450
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-gestures/references/gesture-patterns.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/SKILL.md +336 -336
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/form.md +97 -97
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/grids.md +69 -69
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/list.md +99 -99
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/scrollview.md +147 -147
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-liquid-glass/SKILL.md +325 -325
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-liquid-glass/references/liquid-glass.md +387 -387
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/SKILL.md +262 -262
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/deeplinks.md +207 -207
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/navigationstack.md +177 -177
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/sheets.md +169 -169
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/tabview.md +178 -178
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/SKILL.md +381 -381
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/architecture-patterns.md +486 -486
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/deprecated-migration.md +1097 -1097
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/design-polish.md +780 -780
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/platform-and-sharing.md +696 -696
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/SKILL.md +491 -491
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +46 -46
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +29 -29
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/understanding-hangs-in-your-app.md +33 -33
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/understanding-improving-swiftui-performance.md +52 -52
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/SKILL.md +428 -428
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/references/hosting-migration.md +534 -534
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/references/representable-recipes.md +1133 -1133
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/tipkit/SKILL.md +494 -494
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/tipkit/references/tipkit-patterns.md +782 -782
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/SKILL.md +475 -475
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/vision-requests.md +736 -736
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/visionkit-scanner.md +738 -738
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/SKILL.md +410 -410
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/references/weatherkit-patterns.md +567 -567
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/SKILL.md +497 -497
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/references/widgetkit-advanced.md +871 -871
- package/src/.agents/skills/ui-ux-pro-custom/data/typography.csv +57 -57
- package/src/.agents/skills/ui-ux-pro-custom/data/ui-reasoning.csv +101 -101
- package/src/.agents/skills/ui-ux-pro-custom/data/ux-guidelines.csv +99 -99
- package/src/.agents/skills/ui-ux-pro-custom/data/web-interface.csv +31 -31
- package/src/.agents/skills/ui-ux-pro-custom/scripts/core.py +253 -253
- package/src/.agents/skills/ui-ux-pro-custom/scripts/design_system.py +1067 -1067
- package/src/.agents/skills/ui-ux-pro-custom/scripts/search.py +114 -114
- package/src/.agents/skills/ux-audit/SKILL.md +150 -150
- package/src/.agents/skills/websocket-engineer/SKILL.md +168 -168
- package/src/.agents/skills/websocket-engineer/references/alternatives.md +391 -391
- package/src/.agents/skills/websocket-engineer/references/patterns.md +400 -400
- package/src/.agents/skills/websocket-engineer/references/protocol.md +195 -195
- package/src/.agents/skills/websocket-engineer/references/scaling.md +333 -333
- package/src/.agents/skills/websocket-engineer/references/security.md +474 -474
- package/src/.agents/skills/writing-skills/SKILL.md +655 -655
- package/src/.agents/skills/writing-skills/anthropic-best-practices.md +1150 -1150
- package/src/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -189
- package/src/.agents/skills/writing-skills/graphviz-conventions.dot +171 -171
- package/src/.agents/skills/writing-skills/persuasion-principles.md +187 -187
- package/src/.agents/skills/writing-skills/render-graphs.js +168 -168
- package/src/.agents/skills/writing-skills/testing-skills-with-subagents.md +384 -384
- package/src/.claude/commands/bmad-track-compact.md +1 -1
- package/src/.claude/commands/bmad-track-extended.md +1 -1
- package/src/.claude/commands/bmad-track-large.md +1 -1
- package/src/.claude/commands/bmad-track-medium.md +1 -1
- package/src/.claude/commands/bmad-track-nano.md +1 -1
- package/src/.claude/commands/bmad-track-rv.md +1 -1
- package/src/.claude/commands/bmad-track-small.md +1 -1
- package/src/.claude/commands/master-orchestrator.md +15 -0
- package/src/_memory/config.yaml +11 -11
- package/src/_memory/master-orchestrator-sidecar/instructions.md +85 -32
- package/src/_memory/skills/nimbalyst-tracking/SKILL.md +103 -103
- package/src/_memory/skills/writing-skills/SKILL.md +655 -655
- package/src/bmb/agents/agent-builder.md +59 -59
- package/src/bmb/agents/module-builder.md +60 -60
- package/src/bmb/agents/workflow-builder.md +61 -61
- package/src/bmb/config.yaml +12 -12
- package/src/bmb/module-help.csv +13 -13
- package/src/bmb/workflows/agent/data/agent-architecture.md +258 -258
- package/src/bmb/workflows/agent/data/agent-compilation.md +185 -185
- package/src/bmb/workflows/agent/data/agent-menu-patterns.md +189 -189
- package/src/bmb/workflows/agent/data/agent-metadata.md +133 -133
- package/src/bmb/workflows/agent/data/agent-validation.md +111 -111
- package/src/bmb/workflows/agent/data/brainstorm-context.md +96 -96
- package/src/bmb/workflows/agent/data/communication-presets.csv +61 -61
- package/src/bmb/workflows/agent/data/critical-actions.md +75 -75
- package/src/bmb/workflows/agent/data/persona-properties.md +252 -252
- package/src/bmb/workflows/agent/data/principles-crafting.md +142 -142
- package/src/bmb/workflows/agent/data/reference/module-examples/architect.md +68 -68
- package/src/bmb/workflows/agent/data/reference/with-sidecar/journal-keeper/journal-keeper-sidecar/entries/yy-mm-dd-entry-template.md +16 -16
- package/src/bmb/workflows/agent/data/understanding-agent-types.md +126 -126
- package/src/bmb/workflows/agent/steps-c/step-01-brainstorm.md +129 -129
- package/src/bmb/workflows/agent/steps-c/step-02-discovery.md +170 -170
- package/src/bmb/workflows/agent/steps-c/step-03-sidecar-metadata.md +309 -309
- package/src/bmb/workflows/agent/steps-c/step-04-persona.md +213 -213
- package/src/bmb/workflows/agent/steps-c/step-05-commands-menu.md +179 -179
- package/src/bmb/workflows/agent/steps-c/step-06-activation.md +278 -278
- package/src/bmb/workflows/agent/steps-c/step-07-build-agent.md +316 -316
- package/src/bmb/workflows/agent/steps-c/step-08-celebrate.md +247 -247
- package/src/bmb/workflows/agent/steps-e/e-01-load-existing.md +221 -221
- package/src/bmb/workflows/agent/steps-e/e-02-discover-edits.md +195 -195
- package/src/bmb/workflows/agent/steps-e/e-04-sidecar-metadata.md +126 -126
- package/src/bmb/workflows/agent/steps-e/e-05-persona.md +135 -135
- package/src/bmb/workflows/agent/steps-e/e-06-commands-menu.md +123 -123
- package/src/bmb/workflows/agent/steps-e/e-07-activation.md +124 -124
- package/src/bmb/workflows/agent/steps-e/e-08-edit-agent.md +197 -197
- package/src/bmb/workflows/agent/steps-e/e-09-celebrate.md +155 -155
- package/src/bmb/workflows/agent/steps-v/v-01-load-review.md +137 -137
- package/src/bmb/workflows/agent/steps-v/v-02a-validate-metadata.md +116 -116
- package/src/bmb/workflows/agent/steps-v/v-02b-validate-persona.md +124 -124
- package/src/bmb/workflows/agent/steps-v/v-02c-validate-menu.md +127 -127
- package/src/bmb/workflows/agent/steps-v/v-02d-validate-structure.md +134 -134
- package/src/bmb/workflows/agent/steps-v/v-02e-validate-sidecar.md +134 -134
- package/src/bmb/workflows/agent/steps-v/v-03-summary.md +104 -104
- package/src/bmb/workflows/agent/templates/agent-plan.template.md +5 -5
- package/src/bmb/workflows/agent/templates/agent-template.md +89 -89
- package/src/bmb/workflows/agent/workflow-create-agent.md +72 -72
- package/src/bmb/workflows/agent/workflow-edit-agent.md +75 -75
- package/src/bmb/workflows/agent/workflow-validate-agent.md +73 -73
- package/src/bmb/workflows/module/data/agent-architecture.md +179 -179
- package/src/bmb/workflows/module/data/agent-spec-template.md +79 -79
- package/src/bmb/workflows/module/data/module-standards.md +263 -263
- package/src/bmb/workflows/module/data/module-yaml-conventions.md +392 -392
- package/src/bmb/workflows/module/module-help-generate.md +254 -254
- package/src/bmb/workflows/module/steps-b/step-01-welcome.md +148 -148
- package/src/bmb/workflows/module/steps-b/step-02-spark.md +141 -141
- package/src/bmb/workflows/module/steps-b/step-03-module-type.md +149 -149
- package/src/bmb/workflows/module/steps-b/step-04-vision.md +83 -83
- package/src/bmb/workflows/module/steps-b/step-05-identity.md +97 -97
- package/src/bmb/workflows/module/steps-b/step-06-users.md +86 -86
- package/src/bmb/workflows/module/steps-b/step-07-value.md +76 -76
- package/src/bmb/workflows/module/steps-b/step-08-agents.md +97 -97
- package/src/bmb/workflows/module/steps-b/step-09-workflows.md +83 -83
- package/src/bmb/workflows/module/steps-b/step-10-tools.md +91 -91
- package/src/bmb/workflows/module/steps-b/step-11-scenarios.md +84 -84
- package/src/bmb/workflows/module/steps-b/step-12-creative.md +95 -95
- package/src/bmb/workflows/module/steps-b/step-13-review.md +105 -105
- package/src/bmb/workflows/module/steps-b/step-14-finalize.md +117 -117
- package/src/bmb/workflows/module/steps-c/step-01-load-brief.md +179 -179
- package/src/bmb/workflows/module/steps-c/step-01b-continue.md +82 -82
- package/src/bmb/workflows/module/steps-c/step-02-structure.md +105 -105
- package/src/bmb/workflows/module/steps-c/step-03-config.md +119 -119
- package/src/bmb/workflows/module/steps-c/step-04-agents.md +168 -168
- package/src/bmb/workflows/module/steps-c/step-05-workflows.md +184 -184
- package/src/bmb/workflows/module/steps-c/step-06-docs.md +401 -401
- package/src/bmb/workflows/module/steps-c/step-07-complete.md +152 -152
- package/src/bmb/workflows/module/steps-e/step-01-load-target.md +81 -81
- package/src/bmb/workflows/module/steps-e/step-02-select-edit.md +77 -77
- package/src/bmb/workflows/module/steps-e/step-03-apply-edit.md +77 -77
- package/src/bmb/workflows/module/steps-e/step-04-review.md +80 -80
- package/src/bmb/workflows/module/steps-e/step-05-confirm.md +75 -75
- package/src/bmb/workflows/module/steps-v/step-01-load-target.md +96 -96
- package/src/bmb/workflows/module/steps-v/step-02-file-structure.md +93 -93
- package/src/bmb/workflows/module/steps-v/step-03-module-yaml.md +99 -99
- package/src/bmb/workflows/module/steps-v/step-04-agent-specs.md +152 -152
- package/src/bmb/workflows/module/steps-v/step-05-workflow-specs.md +152 -152
- package/src/bmb/workflows/module/steps-v/step-06-documentation.md +143 -143
- package/src/bmb/workflows/module/steps-v/step-07-installation.md +102 -102
- package/src/bmb/workflows/module/steps-v/step-08-report.md +197 -197
- package/src/bmb/workflows/module/templates/brief-template.md +154 -154
- package/src/bmb/workflows/module/templates/workflow-spec-template.md +96 -96
- package/src/bmb/workflows/module/workflow-create-module-brief.md +71 -71
- package/src/bmb/workflows/module/workflow-create-module.md +86 -86
- package/src/bmb/workflows/module/workflow-edit-module.md +66 -66
- package/src/bmb/workflows/module/workflow-validate-module.md +66 -66
- package/src/bmb/workflows/workflow/data/architecture.md +150 -150
- package/src/bmb/workflows/workflow/data/common-workflow-tools.csv +19 -19
- package/src/bmb/workflows/workflow/data/csv-data-file-standards.md +53 -53
- package/src/bmb/workflows/workflow/data/frontmatter-standards.md +184 -184
- package/src/bmb/workflows/workflow/data/input-discovery-standards.md +191 -191
- package/src/bmb/workflows/workflow/data/intent-vs-prescriptive-spectrum.md +44 -44
- package/src/bmb/workflows/workflow/data/menu-handling-standards.md +133 -133
- package/src/bmb/workflows/workflow/data/output-format-standards.md +135 -135
- package/src/bmb/workflows/workflow/data/step-file-rules.md +235 -235
- package/src/bmb/workflows/workflow/data/step-type-patterns.md +257 -257
- package/src/bmb/workflows/workflow/data/subprocess-optimization-patterns.md +188 -188
- package/src/bmb/workflows/workflow/data/trimodal-workflow-structure.md +164 -164
- package/src/bmb/workflows/workflow/data/workflow-chaining-standards.md +222 -222
- package/src/bmb/workflows/workflow/data/workflow-examples.md +232 -232
- package/src/bmb/workflows/workflow/data/workflow-type-criteria.md +134 -134
- package/src/bmb/workflows/workflow/steps-c/step-00-conversion.md +263 -263
- package/src/bmb/workflows/workflow/steps-c/step-01-discovery.md +194 -194
- package/src/bmb/workflows/workflow/steps-c/step-01b-continuation.md +3 -3
- package/src/bmb/workflows/workflow/steps-c/step-02-classification.md +270 -270
- package/src/bmb/workflows/workflow/steps-c/step-03-requirements.md +283 -283
- package/src/bmb/workflows/workflow/steps-c/step-04-tools.md +282 -282
- package/src/bmb/workflows/workflow/steps-c/step-05-plan-review.md +243 -243
- package/src/bmb/workflows/workflow/steps-c/step-06-design.md +330 -330
- package/src/bmb/workflows/workflow/steps-c/step-07-foundation.md +239 -239
- package/src/bmb/workflows/workflow/steps-c/step-08-build-step-01.md +379 -379
- package/src/bmb/workflows/workflow/steps-c/step-09-build-next-step.md +350 -350
- package/src/bmb/workflows/workflow/steps-c/step-10-confirmation.md +322 -322
- package/src/bmb/workflows/workflow/steps-c/step-11-completion.md +191 -191
- package/src/bmb/workflows/workflow/steps-e/step-e-01-assess-workflow.md +237 -237
- package/src/bmb/workflows/workflow/steps-e/step-e-02-discover-edits.md +251 -251
- package/src/bmb/workflows/workflow/steps-e/step-e-03-fix-validation.md +254 -254
- package/src/bmb/workflows/workflow/steps-e/step-e-04-direct-edit.md +277 -277
- package/src/bmb/workflows/workflow/steps-e/step-e-05-apply-edit.md +154 -154
- package/src/bmb/workflows/workflow/steps-e/step-e-06-validate-after.md +190 -190
- package/src/bmb/workflows/workflow/steps-e/step-e-07-complete.md +206 -206
- package/src/bmb/workflows/workflow/steps-v/step-01-validate-max-mode.md +109 -109
- package/src/bmb/workflows/workflow/steps-v/step-01-validate.md +221 -221
- package/src/bmb/workflows/workflow/steps-v/step-01b-structure.md +152 -152
- package/src/bmb/workflows/workflow/steps-v/step-02-frontmatter-validation.md +199 -199
- package/src/bmb/workflows/workflow/steps-v/step-02b-path-violations.md +265 -265
- package/src/bmb/workflows/workflow/steps-v/step-03-menu-validation.md +164 -164
- package/src/bmb/workflows/workflow/steps-v/step-04-step-type-validation.md +211 -211
- package/src/bmb/workflows/workflow/steps-v/step-05-output-format-validation.md +200 -200
- package/src/bmb/workflows/workflow/steps-v/step-06-validation-design-check.md +195 -195
- package/src/bmb/workflows/workflow/steps-v/step-07-instruction-style-check.md +209 -209
- package/src/bmb/workflows/workflow/steps-v/step-08-collaborative-experience-check.md +199 -199
- package/src/bmb/workflows/workflow/steps-v/step-08b-subprocess-optimization.md +179 -179
- package/src/bmb/workflows/workflow/steps-v/step-09-cohesive-review.md +186 -186
- package/src/bmb/workflows/workflow/steps-v/step-10-report-complete.md +154 -154
- package/src/bmb/workflows/workflow/steps-v/step-11-plan-validation.md +237 -237
- package/src/bmb/workflows/workflow/templates/minimal-output-template.md +11 -11
- package/src/bmb/workflows/workflow/templates/step-01-init-continuable-template.md +241 -241
- package/src/bmb/workflows/workflow/templates/step-1b-template.md +224 -224
- package/src/bmb/workflows/workflow/templates/step-template.md +294 -294
- package/src/bmb/workflows/workflow/templates/workflow-template.md +102 -102
- package/src/bmb/workflows/workflow/workflow-create-workflow.md +79 -79
- package/src/bmb/workflows/workflow/workflow-edit-workflow.md +65 -65
- package/src/bmb/workflows/workflow/workflow-rework-workflow.md +65 -65
- package/src/bmb/workflows/workflow/workflow-validate-max-parallel-workflow.md +66 -66
- package/src/bmb/workflows/workflow/workflow-validate-workflow.md +65 -65
- package/src/bmm/agents/analyst.md +104 -104
- package/src/bmm/agents/dev.md +100 -100
- package/src/bmm/agents/qa.md +100 -90
- package/src/bmm/agents/review-agent.md +1 -1
- package/src/bmm/agents/tech-writer/tech-writer.md +94 -94
- package/src/bmm/module-help.csv +31 -31
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01-init.md +115 -115
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01b-continue.md +107 -107
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +141 -141
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +144 -144
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +147 -147
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +161 -161
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-06-complete.md +99 -99
- package/src/bmm/workflows/1-analysis/create-product-brief/workflow.md +57 -57
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-01-init.md +87 -87
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-02-domain-analysis.md +156 -156
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-03-competitive-landscape.md +165 -165
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-04-regulatory-focus.md +140 -140
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-05-technical-trends.md +152 -152
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-06-research-synthesis.md +345 -345
- package/src/bmm/workflows/1-analysis/research/market-steps/step-01-init.md +92 -92
- package/src/bmm/workflows/1-analysis/research/market-steps/step-02-customer-behavior.md +164 -164
- package/src/bmm/workflows/1-analysis/research/market-steps/step-03-customer-pain-points.md +174 -174
- package/src/bmm/workflows/1-analysis/research/market-steps/step-04-customer-decisions.md +184 -184
- package/src/bmm/workflows/1-analysis/research/market-steps/step-05-competitive-analysis.md +105 -105
- package/src/bmm/workflows/1-analysis/research/market-steps/step-06-research-completion.md +360 -360
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-01-init.md +87 -87
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-02-technical-overview.md +165 -165
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-03-integration-patterns.md +174 -174
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-04-architectural-patterns.md +141 -141
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-05-implementation-research.md +159 -159
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-06-research-synthesis.md +387 -387
- package/src/bmm/workflows/1-analysis/research/workflow-domain-research.md +54 -54
- package/src/bmm/workflows/1-analysis/research/workflow-market-research.md +54 -54
- package/src/bmm/workflows/1-analysis/research/workflow-technical-research.md +54 -54
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-01b-continue.md +100 -100
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02-discovery.md +160 -160
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02b-vision.md +88 -88
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02c-executive-summary.md +99 -99
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-03-success.md +169 -169
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-04-journeys.md +156 -156
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-05-domain.md +136 -136
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-06-innovation.md +176 -176
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-07-project-type.md +184 -184
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-08-scoping.md +174 -174
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-09-functional.md +175 -175
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-10-nonfunctional.md +189 -189
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-11-polish.md +162 -162
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-12-complete.md +79 -79
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01-discovery.md +183 -183
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01b-legacy-conversion.md +149 -149
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-02-review.md +187 -187
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-03-edit.md +192 -192
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-04-complete.md +108 -108
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +166 -166
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02-format-detection.md +131 -131
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02b-parity-check.md +150 -150
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-03-density-validation.md +118 -118
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-04-brief-coverage-validation.md +155 -155
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-05-measurability-validation.md +170 -170
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-06-traceability-validation.md +158 -158
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-07-implementation-leakage-validation.md +147 -147
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-08-domain-compliance-validation.md +182 -182
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-09-project-type-validation.md +202 -202
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +148 -148
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +201 -201
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-12-completeness-validation.md +179 -179
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +164 -164
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-create-prd.md +65 -65
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-edit-prd.md +65 -65
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-validate-prd.md +63 -63
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01b-continue.md +63 -63
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +106 -106
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +111 -111
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +115 -115
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +127 -127
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +167 -167
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +143 -143
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +118 -118
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +154 -154
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +136 -136
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +165 -165
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +135 -135
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +192 -192
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-14-complete.md +101 -101
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +45 -45
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-01-document-discovery.md +185 -185
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-02-prd-analysis.md +129 -129
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-03-epic-coverage-validation.md +130 -130
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-04-ux-alignment.md +93 -93
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-05-epic-quality-review.md +196 -196
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-06-final-assessment.md +129 -129
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md +54 -54
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-01b-continue.md +82 -82
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +106 -106
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +138 -138
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +129 -129
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +166 -166
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +186 -186
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +163 -163
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-08-complete.md +38 -38
- package/src/bmm/workflows/3-solutioning/create-architecture/workflow.md +49 -49
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +124 -124
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +122 -122
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +84 -84
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +58 -58
- package/src/bmm/workflows/4-implementation/code-review/workflow.yaml +43 -43
- package/src/bmm/workflows/4-implementation/correct-course/workflow.yaml +53 -53
- package/src/bmm/workflows/4-implementation/create-story/checklist.md +159 -159
- package/src/bmm/workflows/4-implementation/create-story/template.md +79 -79
- package/src/bmm/workflows/4-implementation/create-story/workflow.yaml +52 -52
- package/src/bmm/workflows/4-implementation/dev-story/workflow.yaml +20 -20
- package/src/bmm/workflows/4-implementation/retrospective/workflow.yaml +52 -52
- package/src/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +52 -52
- package/src/bmm/workflows/4-implementation/sprint-status/workflow.yaml +25 -25
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-01-mode-detection.md +158 -158
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-02-context-gathering.md +122 -122
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-03-execute.md +93 -93
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-04-self-check.md +93 -93
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-05-adversarial-review.md +87 -87
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-06-resolve-findings.md +146 -146
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +50 -50
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-02-investigate.md +152 -152
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-03-generate.md +123 -123
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-04-review.md +201 -201
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +79 -79
- package/src/bmm/workflows/document-project/workflow.yaml +22 -22
- package/src/bmm/workflows/generate-project-context/steps/step-01-discover.md +184 -184
- package/src/bmm/workflows/generate-project-context/steps/step-02-generate.md +322 -322
- package/src/bmm/workflows/generate-project-context/steps/step-03-complete.md +235 -235
- package/src/bmm/workflows/generate-project-context/workflow.md +49 -49
- package/src/bmm/workflows/qa/automate/workflow.yaml +233 -233
- package/src/bmm/workflows/qa-generate-e2e-tests/workflow.yaml +42 -42
- package/src/core/agents/master-orchestrator.md +3 -3
- package/src/core/config.yaml +9 -9
- package/src/core/module-help.csv +10 -10
- package/src/core/scripts/generate-loop-report.py +72 -72
- package/src/core/tasks/editorial-review-prose.xml +101 -101
- package/src/core/tasks/editorial-review-structure.xml +207 -207
- package/src/core/tasks/help.md +86 -86
- package/src/core/tasks/index-docs.xml +64 -64
- package/src/core/tasks/review-adversarial-general.xml +66 -66
- package/src/core/tasks/review-adversarial-loop.xml +46 -46
- package/src/core/tasks/review-edge-case-hunter.xml +63 -63
- package/src/core/tasks/review-party-loop.xml +46 -46
- package/src/core/tasks/shard-doc.xml +107 -107
- package/src/core/tasks/workflow.xml +235 -235
- package/src/core/templates/review-loop-report.html +88 -88
- package/src/core/templates/review-loop-report.md +5 -5
- package/src/core/workflows/advanced-elicitation/workflow.xml +117 -117
- package/src/core/workflows/brainstorming/steps/step-01-session-setup.md +212 -212
- package/src/core/workflows/brainstorming/steps/step-01b-continue.md +122 -122
- package/src/core/workflows/brainstorming/steps/step-02a-user-selected.md +225 -225
- package/src/core/workflows/brainstorming/steps/step-02b-ai-recommended.md +237 -237
- package/src/core/workflows/brainstorming/steps/step-02c-random-selection.md +209 -209
- package/src/core/workflows/brainstorming/steps/step-02d-progressive-flow.md +264 -264
- package/src/core/workflows/brainstorming/steps/step-02e-deep-dive.md +68 -68
- package/src/core/workflows/brainstorming/steps/step-03-technique-execution.md +403 -403
- package/src/core/workflows/brainstorming/steps/step-04-idea-organization.md +303 -303
- package/src/core/workflows/brainstorming/workflow.md +60 -60
- package/src/core/workflows/extract-trackers/workflow.md +45 -45
- package/src/core/workflows/party-mode/steps/step-01-agent-loading.md +142 -142
- package/src/core/workflows/party-mode/workflow.md +194 -194
- package/src/docs/dev/tmux/actions_popup.py +291 -291
- package/src/docs/dev/tmux/tmux-setup.md +62 -1
|
@@ -1,975 +1,975 @@
|
|
|
1
|
-
# SwiftData Advanced Reference
|
|
2
|
-
|
|
3
|
-
Deep reference for custom data stores, history tracking, CloudKit integration,
|
|
4
|
-
Core Data coexistence, batch operations, complex predicates, composite
|
|
5
|
-
attributes, model inheritance, multiple containers, undo/redo, and preview
|
|
6
|
-
patterns.
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## Contents
|
|
11
|
-
|
|
12
|
-
- [Custom Data Stores (iOS 18+)](#custom-data-stores-ios-18)
|
|
13
|
-
- [History Tracking and Change Detection (iOS 18+)](#history-tracking-and-change-detection-ios-18)
|
|
14
|
-
- [CloudKit Integration](#cloudkit-integration)
|
|
15
|
-
- [Core Data Coexistence and Migration](#core-data-coexistence-and-migration)
|
|
16
|
-
- [Batch Operations and Performance](#batch-operations-and-performance)
|
|
17
|
-
- [Complex #Predicate Patterns](#complex-predicate-patterns)
|
|
18
|
-
- [Composite Attributes (iOS 18+)](#composite-attributes-ios-18)
|
|
19
|
-
- [Model Inheritance (iOS 26+)](#model-inheritance-ios-26)
|
|
20
|
-
- [Multiple ModelContainer Configurations](#multiple-modelcontainer-configurations)
|
|
21
|
-
- [Undo/Redo Support](#undoredo-support)
|
|
22
|
-
- [Preview Patterns with In-Memory Stores](#preview-patterns-with-in-memory-stores)
|
|
23
|
-
- [Notification Observation](#notification-observation)
|
|
24
|
-
- [Error Handling](#error-handling)
|
|
25
|
-
|
|
26
|
-
## Custom Data Stores (iOS 18+)
|
|
27
|
-
|
|
28
|
-
### DataStore Protocol
|
|
29
|
-
|
|
30
|
-
Implement the `DataStore` protocol to replace the default SQLite-backed store
|
|
31
|
-
with a custom persistence backend (JSON files, in-memory caches, REST APIs,
|
|
32
|
-
etc.).
|
|
33
|
-
|
|
34
|
-
```swift
|
|
35
|
-
final class JSONStore: DataStore {
|
|
36
|
-
typealias Configuration = JSONStoreConfiguration
|
|
37
|
-
typealias Snapshot = DefaultSnapshot
|
|
38
|
-
|
|
39
|
-
let configuration: JSONStoreConfiguration
|
|
40
|
-
let identifier: String
|
|
41
|
-
let schema: Schema
|
|
42
|
-
|
|
43
|
-
init(_ configuration: JSONStoreConfiguration,
|
|
44
|
-
migrationPlan: (any SchemaMigrationPlan.Type)?) throws {
|
|
45
|
-
self.configuration = configuration
|
|
46
|
-
self.identifier = configuration.name
|
|
47
|
-
self.schema = configuration.schema ?? Schema()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
func fetch<T: PersistentModel>(
|
|
51
|
-
_ request: DataStoreFetchRequest<T>
|
|
52
|
-
) throws -> DataStoreFetchResult<T, DefaultSnapshot> {
|
|
53
|
-
// Load data from JSON file, apply predicate/sort from request.descriptor
|
|
54
|
-
let snapshots: [DefaultSnapshot] = [] // Populate from file
|
|
55
|
-
return DataStoreFetchResult(
|
|
56
|
-
descriptor: request.descriptor,
|
|
57
|
-
fetchedSnapshots: snapshots,
|
|
58
|
-
relatedSnapshots: [:]
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
func fetchCount<T: PersistentModel>(
|
|
63
|
-
_ request: DataStoreFetchRequest<T>
|
|
64
|
-
) throws -> Int {
|
|
65
|
-
try fetch(request).fetchedSnapshots.count
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
func fetchIdentifiers<T: PersistentModel>(
|
|
69
|
-
_ request: DataStoreFetchRequest<T>
|
|
70
|
-
) throws -> [PersistentIdentifier] {
|
|
71
|
-
try fetch(request).fetchedSnapshots.map(\.persistentIdentifier)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
func save(
|
|
75
|
-
_ request: DataStoreSaveChangesRequest<DefaultSnapshot>
|
|
76
|
-
) throws -> DataStoreSaveChangesResult<DefaultSnapshot> {
|
|
77
|
-
// Persist inserted, updated; remove deleted
|
|
78
|
-
return DataStoreSaveChangesResult(
|
|
79
|
-
for: identifier,
|
|
80
|
-
remappedIdentifiers: [:],
|
|
81
|
-
snapshotsToReregister: [:]
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
func erase() throws {
|
|
86
|
-
// Remove all persisted data
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
func initializeState(for editingState: EditingState) {}
|
|
90
|
-
func invalidateState(for editingState: EditingState) {}
|
|
91
|
-
|
|
92
|
-
func cachedSnapshots(
|
|
93
|
-
for identifiers: [PersistentIdentifier],
|
|
94
|
-
editingState: EditingState
|
|
95
|
-
) throws -> [PersistentIdentifier: DefaultSnapshot] {
|
|
96
|
-
[:]
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### DataStoreConfiguration
|
|
102
|
-
|
|
103
|
-
```swift
|
|
104
|
-
struct JSONStoreConfiguration: DataStoreConfiguration {
|
|
105
|
-
typealias Store = JSONStore
|
|
106
|
-
|
|
107
|
-
let name: String
|
|
108
|
-
var schema: Schema?
|
|
109
|
-
let fileURL: URL
|
|
110
|
-
|
|
111
|
-
init(name: String, fileURL: URL) {
|
|
112
|
-
self.name = name
|
|
113
|
-
self.fileURL = fileURL
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
func validate() throws {
|
|
117
|
-
// Validate file URL is accessible
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### Using a Custom Store
|
|
123
|
-
|
|
124
|
-
```swift
|
|
125
|
-
let config = JSONStoreConfiguration(
|
|
126
|
-
name: "JSONStore",
|
|
127
|
-
fileURL: URL.documentsDirectory.appending(path: "data.json")
|
|
128
|
-
)
|
|
129
|
-
let container = try ModelContainer(
|
|
130
|
-
for: Trip.self,
|
|
131
|
-
configurations: config
|
|
132
|
-
)
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Optional Conformances
|
|
136
|
-
|
|
137
|
-
- **`DataStoreBatching`**: Implement `delete(_:)` for batch delete support.
|
|
138
|
-
- **`HistoryProviding`**: Implement `fetchHistory(_:)` and `deleteHistory(_:)`
|
|
139
|
-
for change tracking.
|
|
140
|
-
|
|
141
|
-
### DataStoreError Cases
|
|
142
|
-
|
|
143
|
-
Handle these when implementing custom stores:
|
|
144
|
-
|
|
145
|
-
| Case | Meaning |
|
|
146
|
-
|------|---------|
|
|
147
|
-
| `.invalidPredicate` | Predicate cannot be evaluated by the store |
|
|
148
|
-
| `.preferInMemoryFilter` | Store cannot filter; framework filters in memory |
|
|
149
|
-
| `.preferInMemorySort` | Store cannot sort; framework sorts in memory |
|
|
150
|
-
| `.unsupportedFeature` | Store does not support the requested operation |
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## History Tracking and Change Detection (iOS 18+)
|
|
155
|
-
|
|
156
|
-
### Enable History Tracking
|
|
157
|
-
|
|
158
|
-
Set the `author` property on `ModelContext` to tag changes with an identifier.
|
|
159
|
-
Mark attributes with `.preserveValueOnDeletion` to retain values in tombstones
|
|
160
|
-
after deletion.
|
|
161
|
-
|
|
162
|
-
```swift
|
|
163
|
-
@Model
|
|
164
|
-
class Trip {
|
|
165
|
-
@Attribute(.preserveValueOnDeletion) var name: String
|
|
166
|
-
@Attribute(.preserveValueOnDeletion) var destination: String
|
|
167
|
-
var startDate: Date
|
|
168
|
-
|
|
169
|
-
init(name: String, destination: String, startDate: Date) {
|
|
170
|
-
self.name = name
|
|
171
|
-
self.destination = destination
|
|
172
|
-
self.startDate = startDate
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Tag context for history attribution
|
|
177
|
-
modelContext.author = "mainApp"
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### Fetch History Transactions
|
|
181
|
-
|
|
182
|
-
```swift
|
|
183
|
-
var descriptor = HistoryDescriptor<DefaultHistoryTransaction>()
|
|
184
|
-
|
|
185
|
-
// Filter by token (only new changes since last check)
|
|
186
|
-
if let lastToken = savedToken {
|
|
187
|
-
descriptor.predicate = #Predicate<DefaultHistoryTransaction> { transaction in
|
|
188
|
-
transaction.token > lastToken
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// iOS 26+: Sort by timestamp
|
|
193
|
-
descriptor.sortBy = [SortDescriptor(\.timestamp, order: .reverse)]
|
|
194
|
-
|
|
195
|
-
let transactions = try modelContext.fetchHistory(descriptor)
|
|
196
|
-
|
|
197
|
-
for transaction in transactions {
|
|
198
|
-
for change in transaction.changes {
|
|
199
|
-
switch change {
|
|
200
|
-
case .insert(let insert):
|
|
201
|
-
let insertedID = insert.changedPersistentIdentifier
|
|
202
|
-
// Process new record
|
|
203
|
-
|
|
204
|
-
case .update(let update):
|
|
205
|
-
let updatedID = update.changedPersistentIdentifier
|
|
206
|
-
let changedAttributes = update.updatedAttributes
|
|
207
|
-
// Process modification
|
|
208
|
-
|
|
209
|
-
case .delete(let delete):
|
|
210
|
-
let deletedID = delete.changedPersistentIdentifier
|
|
211
|
-
let tombstone = delete.tombstone
|
|
212
|
-
// Access preserved values
|
|
213
|
-
if let name = tombstone[\.name] as? String {
|
|
214
|
-
// Use preserved name for sync/audit
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Save token for next incremental fetch
|
|
220
|
-
savedToken = transaction.token
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### Delete Stale History
|
|
225
|
-
|
|
226
|
-
```swift
|
|
227
|
-
let cutoffDate = Calendar.current.date(byAdding: .month, value: -3, to: .now)!
|
|
228
|
-
var descriptor = HistoryDescriptor<DefaultHistoryTransaction>()
|
|
229
|
-
descriptor.predicate = #Predicate<DefaultHistoryTransaction> { transaction in
|
|
230
|
-
transaction.timestamp < cutoffDate
|
|
231
|
-
}
|
|
232
|
-
try modelContext.deleteHistory(descriptor)
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
### DefaultHistoryTransaction Properties
|
|
236
|
-
|
|
237
|
-
| Property | Type | Description |
|
|
238
|
-
|----------|------|-------------|
|
|
239
|
-
| `author` | `String?` | The context author that made the change |
|
|
240
|
-
| `changes` | `[HistoryChange]` | Insert, update, delete changes |
|
|
241
|
-
| `storeIdentifier` | `String` | Store that owns the transaction |
|
|
242
|
-
| `timestamp` | `Date` | When the transaction occurred |
|
|
243
|
-
| `token` | `DefaultHistoryToken` | Opaque token for incremental queries |
|
|
244
|
-
| `transactionIdentifier` | ... | Unique transaction ID |
|
|
245
|
-
| `bundleIdentifier` | `String` | Bundle that made the change |
|
|
246
|
-
| `processIdentifier` | `String` | Process that made the change |
|
|
247
|
-
|
|
248
|
-
### Cross-Process Change Detection
|
|
249
|
-
|
|
250
|
-
Use `bundleIdentifier` and `processIdentifier` to differentiate changes from
|
|
251
|
-
widgets, extensions, or the main app.
|
|
252
|
-
|
|
253
|
-
```swift
|
|
254
|
-
for transaction in transactions {
|
|
255
|
-
if transaction.author == "widget" {
|
|
256
|
-
// Handle widget-originated changes
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
---
|
|
262
|
-
|
|
263
|
-
## CloudKit Integration
|
|
264
|
-
|
|
265
|
-
### Configuration Options
|
|
266
|
-
|
|
267
|
-
```swift
|
|
268
|
-
// Automatic: uses CloudKit entitlement from the app
|
|
269
|
-
let autoConfig = ModelConfiguration(
|
|
270
|
-
cloudKitDatabase: .automatic
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
// Explicit private database
|
|
274
|
-
let privateConfig = ModelConfiguration(
|
|
275
|
-
cloudKitDatabase: .private("iCloud.com.example.myapp")
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
// No CloudKit sync
|
|
279
|
-
let localConfig = ModelConfiguration(
|
|
280
|
-
cloudKitDatabase: .none
|
|
281
|
-
)
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
### Setup Requirements
|
|
285
|
-
|
|
286
|
-
1. Enable iCloud capability in Xcode.
|
|
287
|
-
2. Add CloudKit entitlement (`com.apple.developer.icloud-services`).
|
|
288
|
-
3. Configure a CloudKit container identifier.
|
|
289
|
-
4. Use the container identifier in `ModelConfiguration`.
|
|
290
|
-
|
|
291
|
-
### CloudKit-Compatible Model Design
|
|
292
|
-
|
|
293
|
-
```swift
|
|
294
|
-
@Model
|
|
295
|
-
class SyncedNote {
|
|
296
|
-
// Use optional properties -- records may arrive partially from other devices
|
|
297
|
-
var title: String?
|
|
298
|
-
var body: String?
|
|
299
|
-
|
|
300
|
-
// Encrypt sensitive fields in CloudKit
|
|
301
|
-
@Attribute(.allowsCloudEncryption) var secretContent: String?
|
|
302
|
-
|
|
303
|
-
// Store large data externally
|
|
304
|
-
@Attribute(.externalStorage) var attachment: Data?
|
|
305
|
-
|
|
306
|
-
// Avoid .unique with CloudKit -- CloudKit does not enforce server-side uniqueness
|
|
307
|
-
// Use @Attribute(.unique) only for local-only stores
|
|
308
|
-
|
|
309
|
-
init(title: String? = nil, body: String? = nil) {
|
|
310
|
-
self.title = title
|
|
311
|
-
self.body = body
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
### CloudKit Limitations
|
|
317
|
-
|
|
318
|
-
- **Unique constraints**: CloudKit does not enforce uniqueness server-side.
|
|
319
|
-
Avoid `@Attribute(.unique)` and `#Unique` on CloudKit-synced models. Use
|
|
320
|
-
`cloudKitDatabase: .none` for local-only stores that need uniqueness.
|
|
321
|
-
- **Optional properties**: Prefer optionals for all properties on synced models.
|
|
322
|
-
Records from other devices may arrive with missing fields.
|
|
323
|
-
- **Delete rules**: `.cascade` may cause unexpected deletions when sync delivers
|
|
324
|
-
partial data. Test thoroughly.
|
|
325
|
-
- **Schema changes**: CloudKit schemas are additive-only in production. New
|
|
326
|
-
fields are fine; removing or renaming fields requires careful migration.
|
|
327
|
-
|
|
328
|
-
### Multiple Stores: Local + Synced
|
|
329
|
-
|
|
330
|
-
```swift
|
|
331
|
-
let localConfig = ModelConfiguration(
|
|
332
|
-
"Local",
|
|
333
|
-
schema: Schema([DraftNote.self]),
|
|
334
|
-
cloudKitDatabase: .none
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
let syncedConfig = ModelConfiguration(
|
|
338
|
-
"Synced",
|
|
339
|
-
schema: Schema([PublishedNote.self]),
|
|
340
|
-
cloudKitDatabase: .private("iCloud.com.example.app")
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
let container = try ModelContainer(
|
|
344
|
-
for: Schema([DraftNote.self, PublishedNote.self]),
|
|
345
|
-
configurations: [localConfig, syncedConfig]
|
|
346
|
-
)
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
---
|
|
350
|
-
|
|
351
|
-
## Core Data Coexistence and Migration
|
|
352
|
-
|
|
353
|
-
### Three Strategies
|
|
354
|
-
|
|
355
|
-
| Strategy | When to Use |
|
|
356
|
-
|----------|-------------|
|
|
357
|
-
| Pure Core Data | No migration needed; maintain existing stack |
|
|
358
|
-
| Full SwiftData | Greenfield app or complete rewrite |
|
|
359
|
-
| Coexistence | Gradual migration; both stacks share the same store |
|
|
360
|
-
|
|
361
|
-
### Coexistence Setup
|
|
362
|
-
|
|
363
|
-
Both stacks read/write the same SQLite file. Critical requirements:
|
|
364
|
-
|
|
365
|
-
1. **Enable persistent history tracking** on the Core Data side:
|
|
366
|
-
```swift
|
|
367
|
-
let description = NSPersistentStoreDescription()
|
|
368
|
-
description.setOption(
|
|
369
|
-
true as NSNumber,
|
|
370
|
-
forKey: NSPersistentHistoryTrackingKey
|
|
371
|
-
)
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
2. **Match entity names** between Core Data `.xcdatamodeld` and SwiftData
|
|
375
|
-
`@Model` classes.
|
|
376
|
-
|
|
377
|
-
3. **Use different class names** to avoid conflicts:
|
|
378
|
-
```swift
|
|
379
|
-
// Core Data side
|
|
380
|
-
class CDTrip: NSManagedObject { /* ... */ }
|
|
381
|
-
|
|
382
|
-
// SwiftData side
|
|
383
|
-
@Model
|
|
384
|
-
class Trip { /* entity name "Trip" matches Core Data entity */ }
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
4. **Point both stacks at the same store URL**.
|
|
388
|
-
|
|
389
|
-
### Store File Locations
|
|
390
|
-
|
|
391
|
-
| Scenario | Location |
|
|
392
|
-
|----------|----------|
|
|
393
|
-
| Default | Application Support directory |
|
|
394
|
-
| App group entitlement | Root of app group container |
|
|
395
|
-
| Explicit URL | `ModelConfiguration(url: customURL)` |
|
|
396
|
-
|
|
397
|
-
### Migration from Core Data to SwiftData
|
|
398
|
-
|
|
399
|
-
Step-by-step:
|
|
400
|
-
|
|
401
|
-
1. Define `VersionedSchema` matching the current Core Data model.
|
|
402
|
-
2. Create `@Model` classes with matching entity/attribute names.
|
|
403
|
-
3. Set up `SchemaMigrationPlan` for future changes.
|
|
404
|
-
4. Enable persistent history tracking on Core Data side.
|
|
405
|
-
5. Point both stacks at the same store file.
|
|
406
|
-
6. Gradually move reads to `@Query` / `FetchDescriptor`.
|
|
407
|
-
7. Move writes to `ModelContext` operations.
|
|
408
|
-
8. Remove Core Data stack when migration is complete.
|
|
409
|
-
|
|
410
|
-
---
|
|
411
|
-
|
|
412
|
-
## Batch Operations and Performance
|
|
413
|
-
|
|
414
|
-
### Batch Enumeration
|
|
415
|
-
|
|
416
|
-
Process large result sets without loading all objects into memory:
|
|
417
|
-
|
|
418
|
-
```swift
|
|
419
|
-
try modelContext.enumerate(
|
|
420
|
-
FetchDescriptor<Trip>(),
|
|
421
|
-
batchSize: 5000,
|
|
422
|
-
allowEscapingMutations: false
|
|
423
|
-
) { trip in
|
|
424
|
-
trip.isProcessed = true
|
|
425
|
-
}
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
- `batchSize`: Number of objects loaded per batch (default 5000).
|
|
429
|
-
- `allowEscapingMutations`: Set to `true` only if mutations need to persist
|
|
430
|
-
beyond the enumeration block.
|
|
431
|
-
|
|
432
|
-
### Batch Delete
|
|
433
|
-
|
|
434
|
-
```swift
|
|
435
|
-
try modelContext.delete(
|
|
436
|
-
model: Trip.self,
|
|
437
|
-
where: #Predicate { $0.isArchived == true },
|
|
438
|
-
includeSubclasses: true // iOS 26+ with inheritance
|
|
439
|
-
)
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
### Fetching Only Identifiers
|
|
443
|
-
|
|
444
|
-
When full objects are not needed (e.g., for counting or cross-actor references):
|
|
445
|
-
|
|
446
|
-
```swift
|
|
447
|
-
let ids = try modelContext.fetchIdentifiers(FetchDescriptor<Trip>())
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
### Fetch Count
|
|
451
|
-
|
|
452
|
-
```swift
|
|
453
|
-
let count = try modelContext.fetchCount(
|
|
454
|
-
FetchDescriptor<Trip>(predicate: #Predicate { $0.isFavorite == true })
|
|
455
|
-
)
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
### Partial Property Fetch
|
|
459
|
-
|
|
460
|
-
Fetch only specific properties to reduce memory:
|
|
461
|
-
|
|
462
|
-
```swift
|
|
463
|
-
var descriptor = FetchDescriptor<Trip>()
|
|
464
|
-
descriptor.propertiesToFetch = [\.name, \.startDate]
|
|
465
|
-
let trips = try modelContext.fetch(descriptor)
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
### Relationship Prefetching
|
|
469
|
-
|
|
470
|
-
Avoid N+1 query problems by prefetching related objects:
|
|
471
|
-
|
|
472
|
-
```swift
|
|
473
|
-
var descriptor = FetchDescriptor<Trip>()
|
|
474
|
-
descriptor.relationshipKeyPathsForPrefetching = [\.accommodation, \.tags]
|
|
475
|
-
let trips = try modelContext.fetch(descriptor)
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
### Performance Tips
|
|
479
|
-
|
|
480
|
-
- Use `fetchLimit` and `fetchOffset` for pagination.
|
|
481
|
-
- Use `enumerate` instead of `fetch` for processing large datasets.
|
|
482
|
-
- Use `fetchCount` when only the count is needed.
|
|
483
|
-
- Use `fetchIdentifiers` when only IDs are needed.
|
|
484
|
-
- Use `propertiesToFetch` to limit loaded data.
|
|
485
|
-
- Use `@Attribute(.externalStorage)` for `Data` properties over ~100KB.
|
|
486
|
-
- Disable `includePendingChanges` if unsaved data is not needed in results.
|
|
487
|
-
- Call `modelContext.save()` periodically during large imports to flush memory.
|
|
488
|
-
|
|
489
|
-
---
|
|
490
|
-
|
|
491
|
-
## Complex #Predicate Patterns
|
|
492
|
-
|
|
493
|
-
### Nested Collection Predicates
|
|
494
|
-
|
|
495
|
-
```swift
|
|
496
|
-
// Trips with at least one high-priority tag
|
|
497
|
-
#Predicate<Trip> { trip in
|
|
498
|
-
trip.tags.contains { tag in
|
|
499
|
-
tag.priority > 5
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Trips where all items are packed
|
|
504
|
-
#Predicate<Trip> { trip in
|
|
505
|
-
trip.packingList.allSatisfy { item in
|
|
506
|
-
item.isPacked == true
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
### Optional Chaining
|
|
512
|
-
|
|
513
|
-
```swift
|
|
514
|
-
// Trips with accommodation in a specific city
|
|
515
|
-
#Predicate<Trip> { trip in
|
|
516
|
-
trip.accommodation?.city == "Paris"
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// Nil coalescing
|
|
520
|
-
#Predicate<Trip> { trip in
|
|
521
|
-
(trip.accommodation?.rating ?? 0) >= 4
|
|
522
|
-
}
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
### String Operations
|
|
526
|
-
|
|
527
|
-
```swift
|
|
528
|
-
// Case-insensitive search
|
|
529
|
-
#Predicate<Trip> { trip in
|
|
530
|
-
trip.destination.localizedStandardContains(searchText)
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// Prefix matching
|
|
534
|
-
#Predicate<Trip> { trip in
|
|
535
|
-
trip.name.starts(with: "Summer")
|
|
536
|
-
}
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
### Date and Numeric Ranges
|
|
540
|
-
|
|
541
|
-
```swift
|
|
542
|
-
let startOfYear = Calendar.current.date(from: DateComponents(year: 2026, month: 1, day: 1))!
|
|
543
|
-
let endOfYear = Calendar.current.date(from: DateComponents(year: 2026, month: 12, day: 31))!
|
|
544
|
-
|
|
545
|
-
#Predicate<Trip> { trip in
|
|
546
|
-
trip.startDate >= startOfYear && trip.startDate <= endOfYear
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// Arithmetic
|
|
550
|
-
#Predicate<Trip> { trip in
|
|
551
|
-
trip.budget - trip.spent > 100.0
|
|
552
|
-
}
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
### Ternary Expressions
|
|
556
|
-
|
|
557
|
-
```swift
|
|
558
|
-
#Predicate<Trip> { trip in
|
|
559
|
-
(trip.isFavorite ? trip.name : trip.destination).localizedStandardContains(searchText)
|
|
560
|
-
}
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
### Combining Multiple Predicates
|
|
564
|
-
|
|
565
|
-
Build predicates incrementally using captured variables:
|
|
566
|
-
|
|
567
|
-
```swift
|
|
568
|
-
func buildPredicate(
|
|
569
|
-
searchText: String,
|
|
570
|
-
onlyFavorites: Bool,
|
|
571
|
-
minDate: Date?
|
|
572
|
-
) -> Predicate<Trip> {
|
|
573
|
-
#Predicate<Trip> { trip in
|
|
574
|
-
(searchText.isEmpty || trip.name.localizedStandardContains(searchText))
|
|
575
|
-
&& (!onlyFavorites || trip.isFavorite == true)
|
|
576
|
-
&& (minDate == nil || trip.startDate >= (minDate ?? .distantPast))
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
### Type Casting in Predicates (iOS 26+, with Inheritance)
|
|
582
|
-
|
|
583
|
-
```swift
|
|
584
|
-
// Filter for business trips only
|
|
585
|
-
#Predicate<Trip> { trip in
|
|
586
|
-
trip is BusinessTrip
|
|
587
|
-
}
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
---
|
|
591
|
-
|
|
592
|
-
## Composite Attributes (iOS 18+)
|
|
593
|
-
|
|
594
|
-
Codable structs stored as composite (nested) attributes in the database.
|
|
595
|
-
|
|
596
|
-
```swift
|
|
597
|
-
struct Address: Codable {
|
|
598
|
-
var street: String
|
|
599
|
-
var city: String
|
|
600
|
-
var state: String
|
|
601
|
-
var zip: String
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
@Model
|
|
605
|
-
class Person {
|
|
606
|
-
var name: String
|
|
607
|
-
var homeAddress: Address // Stored as composite attribute
|
|
608
|
-
var workAddress: Address?
|
|
609
|
-
|
|
610
|
-
init(name: String, homeAddress: Address) {
|
|
611
|
-
self.name = name
|
|
612
|
-
self.homeAddress = homeAddress
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
Composite attributes appear as `Schema.CompositeAttribute` in the schema.
|
|
618
|
-
Sub-properties are stored inline in the same table. Query individual fields
|
|
619
|
-
via key-path navigation in `#Predicate`:
|
|
620
|
-
|
|
621
|
-
```swift
|
|
622
|
-
#Predicate<Person> { person in
|
|
623
|
-
person.homeAddress.city == "San Francisco"
|
|
624
|
-
}
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
---
|
|
628
|
-
|
|
629
|
-
## Model Inheritance (iOS 26+)
|
|
630
|
-
|
|
631
|
-
### Base and Subclass Pattern
|
|
632
|
-
|
|
633
|
-
```swift
|
|
634
|
-
@Model
|
|
635
|
-
class Trip {
|
|
636
|
-
var name: String
|
|
637
|
-
var destination: String
|
|
638
|
-
var startDate: Date
|
|
639
|
-
var endDate: Date
|
|
640
|
-
|
|
641
|
-
init(name: String, destination: String, startDate: Date, endDate: Date) {
|
|
642
|
-
self.name = name
|
|
643
|
-
self.destination = destination
|
|
644
|
-
self.startDate = startDate
|
|
645
|
-
self.endDate = endDate
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
@Model
|
|
650
|
-
class PersonalTrip: Trip {
|
|
651
|
-
var companion: String?
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
@Model
|
|
655
|
-
class BusinessTrip: Trip {
|
|
656
|
-
var company: String
|
|
657
|
-
var expenseReport: Data?
|
|
658
|
-
|
|
659
|
-
init(name: String, destination: String, startDate: Date, endDate: Date,
|
|
660
|
-
company: String) {
|
|
661
|
-
self.company = company
|
|
662
|
-
super.init(name: name, destination: destination,
|
|
663
|
-
startDate: startDate, endDate: endDate)
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
```
|
|
667
|
-
|
|
668
|
-
### Querying with Inheritance
|
|
669
|
-
|
|
670
|
-
```swift
|
|
671
|
-
// Fetch all trips (includes PersonalTrip and BusinessTrip)
|
|
672
|
-
let allTrips = try modelContext.fetch(FetchDescriptor<Trip>())
|
|
673
|
-
|
|
674
|
-
// Fetch only business trips
|
|
675
|
-
let businessTrips = try modelContext.fetch(FetchDescriptor<BusinessTrip>())
|
|
676
|
-
|
|
677
|
-
// Delete with subclass inclusion
|
|
678
|
-
try modelContext.delete(
|
|
679
|
-
model: Trip.self,
|
|
680
|
-
where: #Predicate { $0.destination == "Cancelled" },
|
|
681
|
-
includeSubclasses: true
|
|
682
|
-
)
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
### Container Registration
|
|
686
|
-
|
|
687
|
-
Register the base class; subclasses are included automatically:
|
|
688
|
-
|
|
689
|
-
```swift
|
|
690
|
-
let container = try ModelContainer(for: Trip.self)
|
|
691
|
-
// PersonalTrip and BusinessTrip are included via inheritance
|
|
692
|
-
```
|
|
693
|
-
|
|
694
|
-
---
|
|
695
|
-
|
|
696
|
-
## Multiple ModelContainer Configurations
|
|
697
|
-
|
|
698
|
-
### Separate Stores for Different Data
|
|
699
|
-
|
|
700
|
-
```swift
|
|
701
|
-
// Local-only data (no sync)
|
|
702
|
-
let localConfig = ModelConfiguration(
|
|
703
|
-
"Local",
|
|
704
|
-
schema: Schema([AppSettings.self, CacheEntry.self]),
|
|
705
|
-
isStoredInMemoryOnly: false,
|
|
706
|
-
cloudKitDatabase: .none
|
|
707
|
-
)
|
|
708
|
-
|
|
709
|
-
// Synced data
|
|
710
|
-
let syncConfig = ModelConfiguration(
|
|
711
|
-
"Synced",
|
|
712
|
-
schema: Schema([UserDocument.self, SharedNote.self]),
|
|
713
|
-
cloudKitDatabase: .private("iCloud.com.example.app")
|
|
714
|
-
)
|
|
715
|
-
|
|
716
|
-
let container = try ModelContainer(
|
|
717
|
-
for: Schema([AppSettings.self, CacheEntry.self, UserDocument.self, SharedNote.self]),
|
|
718
|
-
configurations: [localConfig, syncConfig]
|
|
719
|
-
)
|
|
720
|
-
```
|
|
721
|
-
|
|
722
|
-
### Read-Only Bundled Database
|
|
723
|
-
|
|
724
|
-
```swift
|
|
725
|
-
let bundledURL = Bundle.main.url(forResource: "seed", withExtension: "store")!
|
|
726
|
-
let readOnlyConfig = ModelConfiguration(
|
|
727
|
-
"SeedData",
|
|
728
|
-
schema: Schema([ReferenceItem.self]),
|
|
729
|
-
url: bundledURL,
|
|
730
|
-
allowsSave: false
|
|
731
|
-
)
|
|
732
|
-
```
|
|
733
|
-
|
|
734
|
-
### App Group Sharing (Widget / Extension)
|
|
735
|
-
|
|
736
|
-
```swift
|
|
737
|
-
let sharedConfig = ModelConfiguration(
|
|
738
|
-
groupContainer: .identifier("group.com.example.myapp")
|
|
739
|
-
)
|
|
740
|
-
let container = try ModelContainer(for: Trip.self, configurations: sharedConfig)
|
|
741
|
-
```
|
|
742
|
-
|
|
743
|
-
---
|
|
744
|
-
|
|
745
|
-
## Undo/Redo Support
|
|
746
|
-
|
|
747
|
-
### Setup
|
|
748
|
-
|
|
749
|
-
```swift
|
|
750
|
-
let context = ModelContext(container)
|
|
751
|
-
context.undoManager = UndoManager()
|
|
752
|
-
```
|
|
753
|
-
|
|
754
|
-
### SwiftUI Integration
|
|
755
|
-
|
|
756
|
-
```swift
|
|
757
|
-
@main
|
|
758
|
-
struct MyApp: App {
|
|
759
|
-
let container: ModelContainer
|
|
760
|
-
|
|
761
|
-
init() {
|
|
762
|
-
do {
|
|
763
|
-
container = try ModelContainer(for: Trip.self)
|
|
764
|
-
container.mainContext.undoManager = UndoManager()
|
|
765
|
-
} catch {
|
|
766
|
-
fatalError("Failed to create ModelContainer: \(error)")
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
var body: some Scene {
|
|
771
|
-
WindowGroup {
|
|
772
|
-
ContentView()
|
|
773
|
-
}
|
|
774
|
-
.modelContainer(container)
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
```
|
|
778
|
-
|
|
779
|
-
### Using Undo/Redo
|
|
780
|
-
|
|
781
|
-
```swift
|
|
782
|
-
struct TripEditorView: View {
|
|
783
|
-
@Environment(\.modelContext) private var modelContext
|
|
784
|
-
@Environment(\.undoManager) private var undoManager
|
|
785
|
-
|
|
786
|
-
var body: some View {
|
|
787
|
-
VStack {
|
|
788
|
-
// ... editing UI ...
|
|
789
|
-
}
|
|
790
|
-
.toolbar {
|
|
791
|
-
ToolbarItemGroup {
|
|
792
|
-
Button("Undo") {
|
|
793
|
-
modelContext.undoManager?.undo()
|
|
794
|
-
}
|
|
795
|
-
.disabled(!(modelContext.undoManager?.canUndo ?? false))
|
|
796
|
-
|
|
797
|
-
Button("Redo") {
|
|
798
|
-
modelContext.undoManager?.redo()
|
|
799
|
-
}
|
|
800
|
-
.disabled(!(modelContext.undoManager?.canRedo ?? false))
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
.onAppear {
|
|
804
|
-
modelContext.undoManager = undoManager
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
Process pending changes to register undo actions:
|
|
811
|
-
|
|
812
|
-
```swift
|
|
813
|
-
modelContext.insert(trip)
|
|
814
|
-
modelContext.processPendingChanges()
|
|
815
|
-
// Now undo is available for the insertion
|
|
816
|
-
```
|
|
817
|
-
|
|
818
|
-
---
|
|
819
|
-
|
|
820
|
-
## Preview Patterns with In-Memory Stores
|
|
821
|
-
|
|
822
|
-
### Basic Preview Container
|
|
823
|
-
|
|
824
|
-
```swift
|
|
825
|
-
@MainActor
|
|
826
|
-
let previewContainer: ModelContainer = {
|
|
827
|
-
let config = ModelConfiguration(isStoredInMemoryOnly: true)
|
|
828
|
-
let container = try! ModelContainer(for: Trip.self, configurations: config)
|
|
829
|
-
|
|
830
|
-
// Seed sample data
|
|
831
|
-
let sampleTrips = [
|
|
832
|
-
Trip(name: "Summer in Paris", destination: "Paris",
|
|
833
|
-
startDate: .now, endDate: .now.addingTimeInterval(86400 * 7)),
|
|
834
|
-
Trip(name: "Tokyo Adventure", destination: "Tokyo",
|
|
835
|
-
startDate: .now.addingTimeInterval(86400 * 30),
|
|
836
|
-
endDate: .now.addingTimeInterval(86400 * 37)),
|
|
837
|
-
]
|
|
838
|
-
for trip in sampleTrips {
|
|
839
|
-
container.mainContext.insert(trip)
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
return container
|
|
843
|
-
}()
|
|
844
|
-
|
|
845
|
-
#Preview {
|
|
846
|
-
TripListView()
|
|
847
|
-
.modelContainer(previewContainer)
|
|
848
|
-
}
|
|
849
|
-
```
|
|
850
|
-
|
|
851
|
-
### Preview with Relationships
|
|
852
|
-
|
|
853
|
-
```swift
|
|
854
|
-
#Preview {
|
|
855
|
-
let config = ModelConfiguration(isStoredInMemoryOnly: true)
|
|
856
|
-
let container = try! ModelContainer(
|
|
857
|
-
for: Trip.self, LivingAccommodation.self,
|
|
858
|
-
configurations: config
|
|
859
|
-
)
|
|
860
|
-
|
|
861
|
-
let trip = Trip(name: "Beach Trip", destination: "Malibu",
|
|
862
|
-
startDate: .now, endDate: .now.addingTimeInterval(86400 * 3))
|
|
863
|
-
let hotel = LivingAccommodation(name: "Beach Resort")
|
|
864
|
-
trip.accommodation = hotel
|
|
865
|
-
|
|
866
|
-
container.mainContext.insert(trip)
|
|
867
|
-
|
|
868
|
-
return TripDetailView(trip: trip)
|
|
869
|
-
.modelContainer(container)
|
|
870
|
-
}
|
|
871
|
-
```
|
|
872
|
-
|
|
873
|
-
### Preview Trait (iOS 18+)
|
|
874
|
-
|
|
875
|
-
Use `PreviewModifier` for reusable preview configurations:
|
|
876
|
-
|
|
877
|
-
```swift
|
|
878
|
-
struct SampleDataPreview: PreviewModifier {
|
|
879
|
-
static func makeSharedContext() async throws -> ModelContainer {
|
|
880
|
-
let config = ModelConfiguration(isStoredInMemoryOnly: true)
|
|
881
|
-
let container = try ModelContainer(for: Trip.self, configurations: config)
|
|
882
|
-
// Insert sample data
|
|
883
|
-
return container
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
func body(content: Content, context: ModelContainer) -> some View {
|
|
887
|
-
content.modelContainer(context)
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
extension PreviewTrait where T == Preview.ViewTraits {
|
|
892
|
-
static var sampleData: Self = .modifier(SampleDataPreview())
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
#Preview(traits: .sampleData) {
|
|
896
|
-
TripListView()
|
|
897
|
-
}
|
|
898
|
-
```
|
|
899
|
-
|
|
900
|
-
---
|
|
901
|
-
|
|
902
|
-
## Notification Observation
|
|
903
|
-
|
|
904
|
-
### Observing Save Events
|
|
905
|
-
|
|
906
|
-
```swift
|
|
907
|
-
NotificationCenter.default.publisher(for: ModelContext.didSave, object: modelContext)
|
|
908
|
-
.sink { notification in
|
|
909
|
-
if let insertedIDs = notification.userInfo?[
|
|
910
|
-
ModelContext.NotificationKey.insertedIdentifiers
|
|
911
|
-
] as? Set<PersistentIdentifier> {
|
|
912
|
-
// Handle new insertions
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
if let updatedIDs = notification.userInfo?[
|
|
916
|
-
ModelContext.NotificationKey.updatedIdentifiers
|
|
917
|
-
] as? Set<PersistentIdentifier> {
|
|
918
|
-
// Handle updates
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
if let deletedIDs = notification.userInfo?[
|
|
922
|
-
ModelContext.NotificationKey.deletedIdentifiers
|
|
923
|
-
] as? Set<PersistentIdentifier> {
|
|
924
|
-
// Handle deletions
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
```
|
|
928
|
-
|
|
929
|
-
### Available Notification Keys
|
|
930
|
-
|
|
931
|
-
| Key | Description |
|
|
932
|
-
|-----|-------------|
|
|
933
|
-
| `.insertedIdentifiers` | IDs of newly inserted models |
|
|
934
|
-
| `.updatedIdentifiers` | IDs of updated models |
|
|
935
|
-
| `.deletedIdentifiers` | IDs of deleted models |
|
|
936
|
-
| `.invalidatedAllIdentifiers` | All data invalidated (e.g., store reset) |
|
|
937
|
-
| `.queryGeneration` | Query generation token |
|
|
938
|
-
|
|
939
|
-
---
|
|
940
|
-
|
|
941
|
-
## Error Handling
|
|
942
|
-
|
|
943
|
-
### SwiftDataError Cases
|
|
944
|
-
|
|
945
|
-
```swift
|
|
946
|
-
do {
|
|
947
|
-
let trips = try modelContext.fetch(descriptor)
|
|
948
|
-
} catch let error as SwiftDataError {
|
|
949
|
-
switch error {
|
|
950
|
-
case SwiftDataError.unsupportedPredicate:
|
|
951
|
-
// Predicate uses unsupported operations
|
|
952
|
-
case SwiftDataError.unsupportedSortDescriptor:
|
|
953
|
-
// Sort descriptor cannot be processed
|
|
954
|
-
case SwiftDataError.modelValidationFailure:
|
|
955
|
-
// Model fails validation (e.g., unique constraint)
|
|
956
|
-
case SwiftDataError.loadIssueModelContainer:
|
|
957
|
-
// Container could not load the store
|
|
958
|
-
default:
|
|
959
|
-
// Handle other SwiftData errors
|
|
960
|
-
}
|
|
961
|
-
} catch {
|
|
962
|
-
// Handle non-SwiftData errors
|
|
963
|
-
}
|
|
964
|
-
```
|
|
965
|
-
|
|
966
|
-
### Common Error Categories
|
|
967
|
-
|
|
968
|
-
| Category | Errors |
|
|
969
|
-
|----------|--------|
|
|
970
|
-
| Fetch | `.unsupportedPredicate`, `.unsupportedSortDescriptor`, `.unsupportedKeyPath`, `.includePendingChangesWithBatchSize` |
|
|
971
|
-
| Configuration | `.duplicateConfiguration`, `.configurationFileNameContainsInvalidCharacters`, `.configurationSchemaNotFoundInContainerSchema` |
|
|
972
|
-
| Container | `.loadIssueModelContainer` |
|
|
973
|
-
| Context | `.modelValidationFailure`, `.missingModelContext` |
|
|
974
|
-
| Migration | `.backwardMigration`, `.unknownSchema` |
|
|
975
|
-
| History (iOS 18+) | `.historyTokenExpired`, `.invalidTransactionFetchRequest` |
|
|
1
|
+
# SwiftData Advanced Reference
|
|
2
|
+
|
|
3
|
+
Deep reference for custom data stores, history tracking, CloudKit integration,
|
|
4
|
+
Core Data coexistence, batch operations, complex predicates, composite
|
|
5
|
+
attributes, model inheritance, multiple containers, undo/redo, and preview
|
|
6
|
+
patterns.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Contents
|
|
11
|
+
|
|
12
|
+
- [Custom Data Stores (iOS 18+)](#custom-data-stores-ios-18)
|
|
13
|
+
- [History Tracking and Change Detection (iOS 18+)](#history-tracking-and-change-detection-ios-18)
|
|
14
|
+
- [CloudKit Integration](#cloudkit-integration)
|
|
15
|
+
- [Core Data Coexistence and Migration](#core-data-coexistence-and-migration)
|
|
16
|
+
- [Batch Operations and Performance](#batch-operations-and-performance)
|
|
17
|
+
- [Complex #Predicate Patterns](#complex-predicate-patterns)
|
|
18
|
+
- [Composite Attributes (iOS 18+)](#composite-attributes-ios-18)
|
|
19
|
+
- [Model Inheritance (iOS 26+)](#model-inheritance-ios-26)
|
|
20
|
+
- [Multiple ModelContainer Configurations](#multiple-modelcontainer-configurations)
|
|
21
|
+
- [Undo/Redo Support](#undoredo-support)
|
|
22
|
+
- [Preview Patterns with In-Memory Stores](#preview-patterns-with-in-memory-stores)
|
|
23
|
+
- [Notification Observation](#notification-observation)
|
|
24
|
+
- [Error Handling](#error-handling)
|
|
25
|
+
|
|
26
|
+
## Custom Data Stores (iOS 18+)
|
|
27
|
+
|
|
28
|
+
### DataStore Protocol
|
|
29
|
+
|
|
30
|
+
Implement the `DataStore` protocol to replace the default SQLite-backed store
|
|
31
|
+
with a custom persistence backend (JSON files, in-memory caches, REST APIs,
|
|
32
|
+
etc.).
|
|
33
|
+
|
|
34
|
+
```swift
|
|
35
|
+
final class JSONStore: DataStore {
|
|
36
|
+
typealias Configuration = JSONStoreConfiguration
|
|
37
|
+
typealias Snapshot = DefaultSnapshot
|
|
38
|
+
|
|
39
|
+
let configuration: JSONStoreConfiguration
|
|
40
|
+
let identifier: String
|
|
41
|
+
let schema: Schema
|
|
42
|
+
|
|
43
|
+
init(_ configuration: JSONStoreConfiguration,
|
|
44
|
+
migrationPlan: (any SchemaMigrationPlan.Type)?) throws {
|
|
45
|
+
self.configuration = configuration
|
|
46
|
+
self.identifier = configuration.name
|
|
47
|
+
self.schema = configuration.schema ?? Schema()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func fetch<T: PersistentModel>(
|
|
51
|
+
_ request: DataStoreFetchRequest<T>
|
|
52
|
+
) throws -> DataStoreFetchResult<T, DefaultSnapshot> {
|
|
53
|
+
// Load data from JSON file, apply predicate/sort from request.descriptor
|
|
54
|
+
let snapshots: [DefaultSnapshot] = [] // Populate from file
|
|
55
|
+
return DataStoreFetchResult(
|
|
56
|
+
descriptor: request.descriptor,
|
|
57
|
+
fetchedSnapshots: snapshots,
|
|
58
|
+
relatedSnapshots: [:]
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func fetchCount<T: PersistentModel>(
|
|
63
|
+
_ request: DataStoreFetchRequest<T>
|
|
64
|
+
) throws -> Int {
|
|
65
|
+
try fetch(request).fetchedSnapshots.count
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func fetchIdentifiers<T: PersistentModel>(
|
|
69
|
+
_ request: DataStoreFetchRequest<T>
|
|
70
|
+
) throws -> [PersistentIdentifier] {
|
|
71
|
+
try fetch(request).fetchedSnapshots.map(\.persistentIdentifier)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func save(
|
|
75
|
+
_ request: DataStoreSaveChangesRequest<DefaultSnapshot>
|
|
76
|
+
) throws -> DataStoreSaveChangesResult<DefaultSnapshot> {
|
|
77
|
+
// Persist inserted, updated; remove deleted
|
|
78
|
+
return DataStoreSaveChangesResult(
|
|
79
|
+
for: identifier,
|
|
80
|
+
remappedIdentifiers: [:],
|
|
81
|
+
snapshotsToReregister: [:]
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func erase() throws {
|
|
86
|
+
// Remove all persisted data
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func initializeState(for editingState: EditingState) {}
|
|
90
|
+
func invalidateState(for editingState: EditingState) {}
|
|
91
|
+
|
|
92
|
+
func cachedSnapshots(
|
|
93
|
+
for identifiers: [PersistentIdentifier],
|
|
94
|
+
editingState: EditingState
|
|
95
|
+
) throws -> [PersistentIdentifier: DefaultSnapshot] {
|
|
96
|
+
[:]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### DataStoreConfiguration
|
|
102
|
+
|
|
103
|
+
```swift
|
|
104
|
+
struct JSONStoreConfiguration: DataStoreConfiguration {
|
|
105
|
+
typealias Store = JSONStore
|
|
106
|
+
|
|
107
|
+
let name: String
|
|
108
|
+
var schema: Schema?
|
|
109
|
+
let fileURL: URL
|
|
110
|
+
|
|
111
|
+
init(name: String, fileURL: URL) {
|
|
112
|
+
self.name = name
|
|
113
|
+
self.fileURL = fileURL
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func validate() throws {
|
|
117
|
+
// Validate file URL is accessible
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Using a Custom Store
|
|
123
|
+
|
|
124
|
+
```swift
|
|
125
|
+
let config = JSONStoreConfiguration(
|
|
126
|
+
name: "JSONStore",
|
|
127
|
+
fileURL: URL.documentsDirectory.appending(path: "data.json")
|
|
128
|
+
)
|
|
129
|
+
let container = try ModelContainer(
|
|
130
|
+
for: Trip.self,
|
|
131
|
+
configurations: config
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Optional Conformances
|
|
136
|
+
|
|
137
|
+
- **`DataStoreBatching`**: Implement `delete(_:)` for batch delete support.
|
|
138
|
+
- **`HistoryProviding`**: Implement `fetchHistory(_:)` and `deleteHistory(_:)`
|
|
139
|
+
for change tracking.
|
|
140
|
+
|
|
141
|
+
### DataStoreError Cases
|
|
142
|
+
|
|
143
|
+
Handle these when implementing custom stores:
|
|
144
|
+
|
|
145
|
+
| Case | Meaning |
|
|
146
|
+
|------|---------|
|
|
147
|
+
| `.invalidPredicate` | Predicate cannot be evaluated by the store |
|
|
148
|
+
| `.preferInMemoryFilter` | Store cannot filter; framework filters in memory |
|
|
149
|
+
| `.preferInMemorySort` | Store cannot sort; framework sorts in memory |
|
|
150
|
+
| `.unsupportedFeature` | Store does not support the requested operation |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## History Tracking and Change Detection (iOS 18+)
|
|
155
|
+
|
|
156
|
+
### Enable History Tracking
|
|
157
|
+
|
|
158
|
+
Set the `author` property on `ModelContext` to tag changes with an identifier.
|
|
159
|
+
Mark attributes with `.preserveValueOnDeletion` to retain values in tombstones
|
|
160
|
+
after deletion.
|
|
161
|
+
|
|
162
|
+
```swift
|
|
163
|
+
@Model
|
|
164
|
+
class Trip {
|
|
165
|
+
@Attribute(.preserveValueOnDeletion) var name: String
|
|
166
|
+
@Attribute(.preserveValueOnDeletion) var destination: String
|
|
167
|
+
var startDate: Date
|
|
168
|
+
|
|
169
|
+
init(name: String, destination: String, startDate: Date) {
|
|
170
|
+
self.name = name
|
|
171
|
+
self.destination = destination
|
|
172
|
+
self.startDate = startDate
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Tag context for history attribution
|
|
177
|
+
modelContext.author = "mainApp"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Fetch History Transactions
|
|
181
|
+
|
|
182
|
+
```swift
|
|
183
|
+
var descriptor = HistoryDescriptor<DefaultHistoryTransaction>()
|
|
184
|
+
|
|
185
|
+
// Filter by token (only new changes since last check)
|
|
186
|
+
if let lastToken = savedToken {
|
|
187
|
+
descriptor.predicate = #Predicate<DefaultHistoryTransaction> { transaction in
|
|
188
|
+
transaction.token > lastToken
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// iOS 26+: Sort by timestamp
|
|
193
|
+
descriptor.sortBy = [SortDescriptor(\.timestamp, order: .reverse)]
|
|
194
|
+
|
|
195
|
+
let transactions = try modelContext.fetchHistory(descriptor)
|
|
196
|
+
|
|
197
|
+
for transaction in transactions {
|
|
198
|
+
for change in transaction.changes {
|
|
199
|
+
switch change {
|
|
200
|
+
case .insert(let insert):
|
|
201
|
+
let insertedID = insert.changedPersistentIdentifier
|
|
202
|
+
// Process new record
|
|
203
|
+
|
|
204
|
+
case .update(let update):
|
|
205
|
+
let updatedID = update.changedPersistentIdentifier
|
|
206
|
+
let changedAttributes = update.updatedAttributes
|
|
207
|
+
// Process modification
|
|
208
|
+
|
|
209
|
+
case .delete(let delete):
|
|
210
|
+
let deletedID = delete.changedPersistentIdentifier
|
|
211
|
+
let tombstone = delete.tombstone
|
|
212
|
+
// Access preserved values
|
|
213
|
+
if let name = tombstone[\.name] as? String {
|
|
214
|
+
// Use preserved name for sync/audit
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Save token for next incremental fetch
|
|
220
|
+
savedToken = transaction.token
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Delete Stale History
|
|
225
|
+
|
|
226
|
+
```swift
|
|
227
|
+
let cutoffDate = Calendar.current.date(byAdding: .month, value: -3, to: .now)!
|
|
228
|
+
var descriptor = HistoryDescriptor<DefaultHistoryTransaction>()
|
|
229
|
+
descriptor.predicate = #Predicate<DefaultHistoryTransaction> { transaction in
|
|
230
|
+
transaction.timestamp < cutoffDate
|
|
231
|
+
}
|
|
232
|
+
try modelContext.deleteHistory(descriptor)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### DefaultHistoryTransaction Properties
|
|
236
|
+
|
|
237
|
+
| Property | Type | Description |
|
|
238
|
+
|----------|------|-------------|
|
|
239
|
+
| `author` | `String?` | The context author that made the change |
|
|
240
|
+
| `changes` | `[HistoryChange]` | Insert, update, delete changes |
|
|
241
|
+
| `storeIdentifier` | `String` | Store that owns the transaction |
|
|
242
|
+
| `timestamp` | `Date` | When the transaction occurred |
|
|
243
|
+
| `token` | `DefaultHistoryToken` | Opaque token for incremental queries |
|
|
244
|
+
| `transactionIdentifier` | ... | Unique transaction ID |
|
|
245
|
+
| `bundleIdentifier` | `String` | Bundle that made the change |
|
|
246
|
+
| `processIdentifier` | `String` | Process that made the change |
|
|
247
|
+
|
|
248
|
+
### Cross-Process Change Detection
|
|
249
|
+
|
|
250
|
+
Use `bundleIdentifier` and `processIdentifier` to differentiate changes from
|
|
251
|
+
widgets, extensions, or the main app.
|
|
252
|
+
|
|
253
|
+
```swift
|
|
254
|
+
for transaction in transactions {
|
|
255
|
+
if transaction.author == "widget" {
|
|
256
|
+
// Handle widget-originated changes
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## CloudKit Integration
|
|
264
|
+
|
|
265
|
+
### Configuration Options
|
|
266
|
+
|
|
267
|
+
```swift
|
|
268
|
+
// Automatic: uses CloudKit entitlement from the app
|
|
269
|
+
let autoConfig = ModelConfiguration(
|
|
270
|
+
cloudKitDatabase: .automatic
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
// Explicit private database
|
|
274
|
+
let privateConfig = ModelConfiguration(
|
|
275
|
+
cloudKitDatabase: .private("iCloud.com.example.myapp")
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
// No CloudKit sync
|
|
279
|
+
let localConfig = ModelConfiguration(
|
|
280
|
+
cloudKitDatabase: .none
|
|
281
|
+
)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Setup Requirements
|
|
285
|
+
|
|
286
|
+
1. Enable iCloud capability in Xcode.
|
|
287
|
+
2. Add CloudKit entitlement (`com.apple.developer.icloud-services`).
|
|
288
|
+
3. Configure a CloudKit container identifier.
|
|
289
|
+
4. Use the container identifier in `ModelConfiguration`.
|
|
290
|
+
|
|
291
|
+
### CloudKit-Compatible Model Design
|
|
292
|
+
|
|
293
|
+
```swift
|
|
294
|
+
@Model
|
|
295
|
+
class SyncedNote {
|
|
296
|
+
// Use optional properties -- records may arrive partially from other devices
|
|
297
|
+
var title: String?
|
|
298
|
+
var body: String?
|
|
299
|
+
|
|
300
|
+
// Encrypt sensitive fields in CloudKit
|
|
301
|
+
@Attribute(.allowsCloudEncryption) var secretContent: String?
|
|
302
|
+
|
|
303
|
+
// Store large data externally
|
|
304
|
+
@Attribute(.externalStorage) var attachment: Data?
|
|
305
|
+
|
|
306
|
+
// Avoid .unique with CloudKit -- CloudKit does not enforce server-side uniqueness
|
|
307
|
+
// Use @Attribute(.unique) only for local-only stores
|
|
308
|
+
|
|
309
|
+
init(title: String? = nil, body: String? = nil) {
|
|
310
|
+
self.title = title
|
|
311
|
+
self.body = body
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### CloudKit Limitations
|
|
317
|
+
|
|
318
|
+
- **Unique constraints**: CloudKit does not enforce uniqueness server-side.
|
|
319
|
+
Avoid `@Attribute(.unique)` and `#Unique` on CloudKit-synced models. Use
|
|
320
|
+
`cloudKitDatabase: .none` for local-only stores that need uniqueness.
|
|
321
|
+
- **Optional properties**: Prefer optionals for all properties on synced models.
|
|
322
|
+
Records from other devices may arrive with missing fields.
|
|
323
|
+
- **Delete rules**: `.cascade` may cause unexpected deletions when sync delivers
|
|
324
|
+
partial data. Test thoroughly.
|
|
325
|
+
- **Schema changes**: CloudKit schemas are additive-only in production. New
|
|
326
|
+
fields are fine; removing or renaming fields requires careful migration.
|
|
327
|
+
|
|
328
|
+
### Multiple Stores: Local + Synced
|
|
329
|
+
|
|
330
|
+
```swift
|
|
331
|
+
let localConfig = ModelConfiguration(
|
|
332
|
+
"Local",
|
|
333
|
+
schema: Schema([DraftNote.self]),
|
|
334
|
+
cloudKitDatabase: .none
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
let syncedConfig = ModelConfiguration(
|
|
338
|
+
"Synced",
|
|
339
|
+
schema: Schema([PublishedNote.self]),
|
|
340
|
+
cloudKitDatabase: .private("iCloud.com.example.app")
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
let container = try ModelContainer(
|
|
344
|
+
for: Schema([DraftNote.self, PublishedNote.self]),
|
|
345
|
+
configurations: [localConfig, syncedConfig]
|
|
346
|
+
)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Core Data Coexistence and Migration
|
|
352
|
+
|
|
353
|
+
### Three Strategies
|
|
354
|
+
|
|
355
|
+
| Strategy | When to Use |
|
|
356
|
+
|----------|-------------|
|
|
357
|
+
| Pure Core Data | No migration needed; maintain existing stack |
|
|
358
|
+
| Full SwiftData | Greenfield app or complete rewrite |
|
|
359
|
+
| Coexistence | Gradual migration; both stacks share the same store |
|
|
360
|
+
|
|
361
|
+
### Coexistence Setup
|
|
362
|
+
|
|
363
|
+
Both stacks read/write the same SQLite file. Critical requirements:
|
|
364
|
+
|
|
365
|
+
1. **Enable persistent history tracking** on the Core Data side:
|
|
366
|
+
```swift
|
|
367
|
+
let description = NSPersistentStoreDescription()
|
|
368
|
+
description.setOption(
|
|
369
|
+
true as NSNumber,
|
|
370
|
+
forKey: NSPersistentHistoryTrackingKey
|
|
371
|
+
)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
2. **Match entity names** between Core Data `.xcdatamodeld` and SwiftData
|
|
375
|
+
`@Model` classes.
|
|
376
|
+
|
|
377
|
+
3. **Use different class names** to avoid conflicts:
|
|
378
|
+
```swift
|
|
379
|
+
// Core Data side
|
|
380
|
+
class CDTrip: NSManagedObject { /* ... */ }
|
|
381
|
+
|
|
382
|
+
// SwiftData side
|
|
383
|
+
@Model
|
|
384
|
+
class Trip { /* entity name "Trip" matches Core Data entity */ }
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
4. **Point both stacks at the same store URL**.
|
|
388
|
+
|
|
389
|
+
### Store File Locations
|
|
390
|
+
|
|
391
|
+
| Scenario | Location |
|
|
392
|
+
|----------|----------|
|
|
393
|
+
| Default | Application Support directory |
|
|
394
|
+
| App group entitlement | Root of app group container |
|
|
395
|
+
| Explicit URL | `ModelConfiguration(url: customURL)` |
|
|
396
|
+
|
|
397
|
+
### Migration from Core Data to SwiftData
|
|
398
|
+
|
|
399
|
+
Step-by-step:
|
|
400
|
+
|
|
401
|
+
1. Define `VersionedSchema` matching the current Core Data model.
|
|
402
|
+
2. Create `@Model` classes with matching entity/attribute names.
|
|
403
|
+
3. Set up `SchemaMigrationPlan` for future changes.
|
|
404
|
+
4. Enable persistent history tracking on Core Data side.
|
|
405
|
+
5. Point both stacks at the same store file.
|
|
406
|
+
6. Gradually move reads to `@Query` / `FetchDescriptor`.
|
|
407
|
+
7. Move writes to `ModelContext` operations.
|
|
408
|
+
8. Remove Core Data stack when migration is complete.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Batch Operations and Performance
|
|
413
|
+
|
|
414
|
+
### Batch Enumeration
|
|
415
|
+
|
|
416
|
+
Process large result sets without loading all objects into memory:
|
|
417
|
+
|
|
418
|
+
```swift
|
|
419
|
+
try modelContext.enumerate(
|
|
420
|
+
FetchDescriptor<Trip>(),
|
|
421
|
+
batchSize: 5000,
|
|
422
|
+
allowEscapingMutations: false
|
|
423
|
+
) { trip in
|
|
424
|
+
trip.isProcessed = true
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
- `batchSize`: Number of objects loaded per batch (default 5000).
|
|
429
|
+
- `allowEscapingMutations`: Set to `true` only if mutations need to persist
|
|
430
|
+
beyond the enumeration block.
|
|
431
|
+
|
|
432
|
+
### Batch Delete
|
|
433
|
+
|
|
434
|
+
```swift
|
|
435
|
+
try modelContext.delete(
|
|
436
|
+
model: Trip.self,
|
|
437
|
+
where: #Predicate { $0.isArchived == true },
|
|
438
|
+
includeSubclasses: true // iOS 26+ with inheritance
|
|
439
|
+
)
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Fetching Only Identifiers
|
|
443
|
+
|
|
444
|
+
When full objects are not needed (e.g., for counting or cross-actor references):
|
|
445
|
+
|
|
446
|
+
```swift
|
|
447
|
+
let ids = try modelContext.fetchIdentifiers(FetchDescriptor<Trip>())
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Fetch Count
|
|
451
|
+
|
|
452
|
+
```swift
|
|
453
|
+
let count = try modelContext.fetchCount(
|
|
454
|
+
FetchDescriptor<Trip>(predicate: #Predicate { $0.isFavorite == true })
|
|
455
|
+
)
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Partial Property Fetch
|
|
459
|
+
|
|
460
|
+
Fetch only specific properties to reduce memory:
|
|
461
|
+
|
|
462
|
+
```swift
|
|
463
|
+
var descriptor = FetchDescriptor<Trip>()
|
|
464
|
+
descriptor.propertiesToFetch = [\.name, \.startDate]
|
|
465
|
+
let trips = try modelContext.fetch(descriptor)
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Relationship Prefetching
|
|
469
|
+
|
|
470
|
+
Avoid N+1 query problems by prefetching related objects:
|
|
471
|
+
|
|
472
|
+
```swift
|
|
473
|
+
var descriptor = FetchDescriptor<Trip>()
|
|
474
|
+
descriptor.relationshipKeyPathsForPrefetching = [\.accommodation, \.tags]
|
|
475
|
+
let trips = try modelContext.fetch(descriptor)
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Performance Tips
|
|
479
|
+
|
|
480
|
+
- Use `fetchLimit` and `fetchOffset` for pagination.
|
|
481
|
+
- Use `enumerate` instead of `fetch` for processing large datasets.
|
|
482
|
+
- Use `fetchCount` when only the count is needed.
|
|
483
|
+
- Use `fetchIdentifiers` when only IDs are needed.
|
|
484
|
+
- Use `propertiesToFetch` to limit loaded data.
|
|
485
|
+
- Use `@Attribute(.externalStorage)` for `Data` properties over ~100KB.
|
|
486
|
+
- Disable `includePendingChanges` if unsaved data is not needed in results.
|
|
487
|
+
- Call `modelContext.save()` periodically during large imports to flush memory.
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## Complex #Predicate Patterns
|
|
492
|
+
|
|
493
|
+
### Nested Collection Predicates
|
|
494
|
+
|
|
495
|
+
```swift
|
|
496
|
+
// Trips with at least one high-priority tag
|
|
497
|
+
#Predicate<Trip> { trip in
|
|
498
|
+
trip.tags.contains { tag in
|
|
499
|
+
tag.priority > 5
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Trips where all items are packed
|
|
504
|
+
#Predicate<Trip> { trip in
|
|
505
|
+
trip.packingList.allSatisfy { item in
|
|
506
|
+
item.isPacked == true
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Optional Chaining
|
|
512
|
+
|
|
513
|
+
```swift
|
|
514
|
+
// Trips with accommodation in a specific city
|
|
515
|
+
#Predicate<Trip> { trip in
|
|
516
|
+
trip.accommodation?.city == "Paris"
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Nil coalescing
|
|
520
|
+
#Predicate<Trip> { trip in
|
|
521
|
+
(trip.accommodation?.rating ?? 0) >= 4
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### String Operations
|
|
526
|
+
|
|
527
|
+
```swift
|
|
528
|
+
// Case-insensitive search
|
|
529
|
+
#Predicate<Trip> { trip in
|
|
530
|
+
trip.destination.localizedStandardContains(searchText)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Prefix matching
|
|
534
|
+
#Predicate<Trip> { trip in
|
|
535
|
+
trip.name.starts(with: "Summer")
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Date and Numeric Ranges
|
|
540
|
+
|
|
541
|
+
```swift
|
|
542
|
+
let startOfYear = Calendar.current.date(from: DateComponents(year: 2026, month: 1, day: 1))!
|
|
543
|
+
let endOfYear = Calendar.current.date(from: DateComponents(year: 2026, month: 12, day: 31))!
|
|
544
|
+
|
|
545
|
+
#Predicate<Trip> { trip in
|
|
546
|
+
trip.startDate >= startOfYear && trip.startDate <= endOfYear
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Arithmetic
|
|
550
|
+
#Predicate<Trip> { trip in
|
|
551
|
+
trip.budget - trip.spent > 100.0
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Ternary Expressions
|
|
556
|
+
|
|
557
|
+
```swift
|
|
558
|
+
#Predicate<Trip> { trip in
|
|
559
|
+
(trip.isFavorite ? trip.name : trip.destination).localizedStandardContains(searchText)
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### Combining Multiple Predicates
|
|
564
|
+
|
|
565
|
+
Build predicates incrementally using captured variables:
|
|
566
|
+
|
|
567
|
+
```swift
|
|
568
|
+
func buildPredicate(
|
|
569
|
+
searchText: String,
|
|
570
|
+
onlyFavorites: Bool,
|
|
571
|
+
minDate: Date?
|
|
572
|
+
) -> Predicate<Trip> {
|
|
573
|
+
#Predicate<Trip> { trip in
|
|
574
|
+
(searchText.isEmpty || trip.name.localizedStandardContains(searchText))
|
|
575
|
+
&& (!onlyFavorites || trip.isFavorite == true)
|
|
576
|
+
&& (minDate == nil || trip.startDate >= (minDate ?? .distantPast))
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### Type Casting in Predicates (iOS 26+, with Inheritance)
|
|
582
|
+
|
|
583
|
+
```swift
|
|
584
|
+
// Filter for business trips only
|
|
585
|
+
#Predicate<Trip> { trip in
|
|
586
|
+
trip is BusinessTrip
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
## Composite Attributes (iOS 18+)
|
|
593
|
+
|
|
594
|
+
Codable structs stored as composite (nested) attributes in the database.
|
|
595
|
+
|
|
596
|
+
```swift
|
|
597
|
+
struct Address: Codable {
|
|
598
|
+
var street: String
|
|
599
|
+
var city: String
|
|
600
|
+
var state: String
|
|
601
|
+
var zip: String
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
@Model
|
|
605
|
+
class Person {
|
|
606
|
+
var name: String
|
|
607
|
+
var homeAddress: Address // Stored as composite attribute
|
|
608
|
+
var workAddress: Address?
|
|
609
|
+
|
|
610
|
+
init(name: String, homeAddress: Address) {
|
|
611
|
+
self.name = name
|
|
612
|
+
self.homeAddress = homeAddress
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
Composite attributes appear as `Schema.CompositeAttribute` in the schema.
|
|
618
|
+
Sub-properties are stored inline in the same table. Query individual fields
|
|
619
|
+
via key-path navigation in `#Predicate`:
|
|
620
|
+
|
|
621
|
+
```swift
|
|
622
|
+
#Predicate<Person> { person in
|
|
623
|
+
person.homeAddress.city == "San Francisco"
|
|
624
|
+
}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## Model Inheritance (iOS 26+)
|
|
630
|
+
|
|
631
|
+
### Base and Subclass Pattern
|
|
632
|
+
|
|
633
|
+
```swift
|
|
634
|
+
@Model
|
|
635
|
+
class Trip {
|
|
636
|
+
var name: String
|
|
637
|
+
var destination: String
|
|
638
|
+
var startDate: Date
|
|
639
|
+
var endDate: Date
|
|
640
|
+
|
|
641
|
+
init(name: String, destination: String, startDate: Date, endDate: Date) {
|
|
642
|
+
self.name = name
|
|
643
|
+
self.destination = destination
|
|
644
|
+
self.startDate = startDate
|
|
645
|
+
self.endDate = endDate
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
@Model
|
|
650
|
+
class PersonalTrip: Trip {
|
|
651
|
+
var companion: String?
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
@Model
|
|
655
|
+
class BusinessTrip: Trip {
|
|
656
|
+
var company: String
|
|
657
|
+
var expenseReport: Data?
|
|
658
|
+
|
|
659
|
+
init(name: String, destination: String, startDate: Date, endDate: Date,
|
|
660
|
+
company: String) {
|
|
661
|
+
self.company = company
|
|
662
|
+
super.init(name: name, destination: destination,
|
|
663
|
+
startDate: startDate, endDate: endDate)
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### Querying with Inheritance
|
|
669
|
+
|
|
670
|
+
```swift
|
|
671
|
+
// Fetch all trips (includes PersonalTrip and BusinessTrip)
|
|
672
|
+
let allTrips = try modelContext.fetch(FetchDescriptor<Trip>())
|
|
673
|
+
|
|
674
|
+
// Fetch only business trips
|
|
675
|
+
let businessTrips = try modelContext.fetch(FetchDescriptor<BusinessTrip>())
|
|
676
|
+
|
|
677
|
+
// Delete with subclass inclusion
|
|
678
|
+
try modelContext.delete(
|
|
679
|
+
model: Trip.self,
|
|
680
|
+
where: #Predicate { $0.destination == "Cancelled" },
|
|
681
|
+
includeSubclasses: true
|
|
682
|
+
)
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### Container Registration
|
|
686
|
+
|
|
687
|
+
Register the base class; subclasses are included automatically:
|
|
688
|
+
|
|
689
|
+
```swift
|
|
690
|
+
let container = try ModelContainer(for: Trip.self)
|
|
691
|
+
// PersonalTrip and BusinessTrip are included via inheritance
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
## Multiple ModelContainer Configurations
|
|
697
|
+
|
|
698
|
+
### Separate Stores for Different Data
|
|
699
|
+
|
|
700
|
+
```swift
|
|
701
|
+
// Local-only data (no sync)
|
|
702
|
+
let localConfig = ModelConfiguration(
|
|
703
|
+
"Local",
|
|
704
|
+
schema: Schema([AppSettings.self, CacheEntry.self]),
|
|
705
|
+
isStoredInMemoryOnly: false,
|
|
706
|
+
cloudKitDatabase: .none
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
// Synced data
|
|
710
|
+
let syncConfig = ModelConfiguration(
|
|
711
|
+
"Synced",
|
|
712
|
+
schema: Schema([UserDocument.self, SharedNote.self]),
|
|
713
|
+
cloudKitDatabase: .private("iCloud.com.example.app")
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
let container = try ModelContainer(
|
|
717
|
+
for: Schema([AppSettings.self, CacheEntry.self, UserDocument.self, SharedNote.self]),
|
|
718
|
+
configurations: [localConfig, syncConfig]
|
|
719
|
+
)
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
### Read-Only Bundled Database
|
|
723
|
+
|
|
724
|
+
```swift
|
|
725
|
+
let bundledURL = Bundle.main.url(forResource: "seed", withExtension: "store")!
|
|
726
|
+
let readOnlyConfig = ModelConfiguration(
|
|
727
|
+
"SeedData",
|
|
728
|
+
schema: Schema([ReferenceItem.self]),
|
|
729
|
+
url: bundledURL,
|
|
730
|
+
allowsSave: false
|
|
731
|
+
)
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
### App Group Sharing (Widget / Extension)
|
|
735
|
+
|
|
736
|
+
```swift
|
|
737
|
+
let sharedConfig = ModelConfiguration(
|
|
738
|
+
groupContainer: .identifier("group.com.example.myapp")
|
|
739
|
+
)
|
|
740
|
+
let container = try ModelContainer(for: Trip.self, configurations: sharedConfig)
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
---
|
|
744
|
+
|
|
745
|
+
## Undo/Redo Support
|
|
746
|
+
|
|
747
|
+
### Setup
|
|
748
|
+
|
|
749
|
+
```swift
|
|
750
|
+
let context = ModelContext(container)
|
|
751
|
+
context.undoManager = UndoManager()
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### SwiftUI Integration
|
|
755
|
+
|
|
756
|
+
```swift
|
|
757
|
+
@main
|
|
758
|
+
struct MyApp: App {
|
|
759
|
+
let container: ModelContainer
|
|
760
|
+
|
|
761
|
+
init() {
|
|
762
|
+
do {
|
|
763
|
+
container = try ModelContainer(for: Trip.self)
|
|
764
|
+
container.mainContext.undoManager = UndoManager()
|
|
765
|
+
} catch {
|
|
766
|
+
fatalError("Failed to create ModelContainer: \(error)")
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
var body: some Scene {
|
|
771
|
+
WindowGroup {
|
|
772
|
+
ContentView()
|
|
773
|
+
}
|
|
774
|
+
.modelContainer(container)
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
### Using Undo/Redo
|
|
780
|
+
|
|
781
|
+
```swift
|
|
782
|
+
struct TripEditorView: View {
|
|
783
|
+
@Environment(\.modelContext) private var modelContext
|
|
784
|
+
@Environment(\.undoManager) private var undoManager
|
|
785
|
+
|
|
786
|
+
var body: some View {
|
|
787
|
+
VStack {
|
|
788
|
+
// ... editing UI ...
|
|
789
|
+
}
|
|
790
|
+
.toolbar {
|
|
791
|
+
ToolbarItemGroup {
|
|
792
|
+
Button("Undo") {
|
|
793
|
+
modelContext.undoManager?.undo()
|
|
794
|
+
}
|
|
795
|
+
.disabled(!(modelContext.undoManager?.canUndo ?? false))
|
|
796
|
+
|
|
797
|
+
Button("Redo") {
|
|
798
|
+
modelContext.undoManager?.redo()
|
|
799
|
+
}
|
|
800
|
+
.disabled(!(modelContext.undoManager?.canRedo ?? false))
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
.onAppear {
|
|
804
|
+
modelContext.undoManager = undoManager
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
Process pending changes to register undo actions:
|
|
811
|
+
|
|
812
|
+
```swift
|
|
813
|
+
modelContext.insert(trip)
|
|
814
|
+
modelContext.processPendingChanges()
|
|
815
|
+
// Now undo is available for the insertion
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
---
|
|
819
|
+
|
|
820
|
+
## Preview Patterns with In-Memory Stores
|
|
821
|
+
|
|
822
|
+
### Basic Preview Container
|
|
823
|
+
|
|
824
|
+
```swift
|
|
825
|
+
@MainActor
|
|
826
|
+
let previewContainer: ModelContainer = {
|
|
827
|
+
let config = ModelConfiguration(isStoredInMemoryOnly: true)
|
|
828
|
+
let container = try! ModelContainer(for: Trip.self, configurations: config)
|
|
829
|
+
|
|
830
|
+
// Seed sample data
|
|
831
|
+
let sampleTrips = [
|
|
832
|
+
Trip(name: "Summer in Paris", destination: "Paris",
|
|
833
|
+
startDate: .now, endDate: .now.addingTimeInterval(86400 * 7)),
|
|
834
|
+
Trip(name: "Tokyo Adventure", destination: "Tokyo",
|
|
835
|
+
startDate: .now.addingTimeInterval(86400 * 30),
|
|
836
|
+
endDate: .now.addingTimeInterval(86400 * 37)),
|
|
837
|
+
]
|
|
838
|
+
for trip in sampleTrips {
|
|
839
|
+
container.mainContext.insert(trip)
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
return container
|
|
843
|
+
}()
|
|
844
|
+
|
|
845
|
+
#Preview {
|
|
846
|
+
TripListView()
|
|
847
|
+
.modelContainer(previewContainer)
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
### Preview with Relationships
|
|
852
|
+
|
|
853
|
+
```swift
|
|
854
|
+
#Preview {
|
|
855
|
+
let config = ModelConfiguration(isStoredInMemoryOnly: true)
|
|
856
|
+
let container = try! ModelContainer(
|
|
857
|
+
for: Trip.self, LivingAccommodation.self,
|
|
858
|
+
configurations: config
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
let trip = Trip(name: "Beach Trip", destination: "Malibu",
|
|
862
|
+
startDate: .now, endDate: .now.addingTimeInterval(86400 * 3))
|
|
863
|
+
let hotel = LivingAccommodation(name: "Beach Resort")
|
|
864
|
+
trip.accommodation = hotel
|
|
865
|
+
|
|
866
|
+
container.mainContext.insert(trip)
|
|
867
|
+
|
|
868
|
+
return TripDetailView(trip: trip)
|
|
869
|
+
.modelContainer(container)
|
|
870
|
+
}
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
### Preview Trait (iOS 18+)
|
|
874
|
+
|
|
875
|
+
Use `PreviewModifier` for reusable preview configurations:
|
|
876
|
+
|
|
877
|
+
```swift
|
|
878
|
+
struct SampleDataPreview: PreviewModifier {
|
|
879
|
+
static func makeSharedContext() async throws -> ModelContainer {
|
|
880
|
+
let config = ModelConfiguration(isStoredInMemoryOnly: true)
|
|
881
|
+
let container = try ModelContainer(for: Trip.self, configurations: config)
|
|
882
|
+
// Insert sample data
|
|
883
|
+
return container
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
func body(content: Content, context: ModelContainer) -> some View {
|
|
887
|
+
content.modelContainer(context)
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
extension PreviewTrait where T == Preview.ViewTraits {
|
|
892
|
+
static var sampleData: Self = .modifier(SampleDataPreview())
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
#Preview(traits: .sampleData) {
|
|
896
|
+
TripListView()
|
|
897
|
+
}
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
---
|
|
901
|
+
|
|
902
|
+
## Notification Observation
|
|
903
|
+
|
|
904
|
+
### Observing Save Events
|
|
905
|
+
|
|
906
|
+
```swift
|
|
907
|
+
NotificationCenter.default.publisher(for: ModelContext.didSave, object: modelContext)
|
|
908
|
+
.sink { notification in
|
|
909
|
+
if let insertedIDs = notification.userInfo?[
|
|
910
|
+
ModelContext.NotificationKey.insertedIdentifiers
|
|
911
|
+
] as? Set<PersistentIdentifier> {
|
|
912
|
+
// Handle new insertions
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if let updatedIDs = notification.userInfo?[
|
|
916
|
+
ModelContext.NotificationKey.updatedIdentifiers
|
|
917
|
+
] as? Set<PersistentIdentifier> {
|
|
918
|
+
// Handle updates
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if let deletedIDs = notification.userInfo?[
|
|
922
|
+
ModelContext.NotificationKey.deletedIdentifiers
|
|
923
|
+
] as? Set<PersistentIdentifier> {
|
|
924
|
+
// Handle deletions
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
### Available Notification Keys
|
|
930
|
+
|
|
931
|
+
| Key | Description |
|
|
932
|
+
|-----|-------------|
|
|
933
|
+
| `.insertedIdentifiers` | IDs of newly inserted models |
|
|
934
|
+
| `.updatedIdentifiers` | IDs of updated models |
|
|
935
|
+
| `.deletedIdentifiers` | IDs of deleted models |
|
|
936
|
+
| `.invalidatedAllIdentifiers` | All data invalidated (e.g., store reset) |
|
|
937
|
+
| `.queryGeneration` | Query generation token |
|
|
938
|
+
|
|
939
|
+
---
|
|
940
|
+
|
|
941
|
+
## Error Handling
|
|
942
|
+
|
|
943
|
+
### SwiftDataError Cases
|
|
944
|
+
|
|
945
|
+
```swift
|
|
946
|
+
do {
|
|
947
|
+
let trips = try modelContext.fetch(descriptor)
|
|
948
|
+
} catch let error as SwiftDataError {
|
|
949
|
+
switch error {
|
|
950
|
+
case SwiftDataError.unsupportedPredicate:
|
|
951
|
+
// Predicate uses unsupported operations
|
|
952
|
+
case SwiftDataError.unsupportedSortDescriptor:
|
|
953
|
+
// Sort descriptor cannot be processed
|
|
954
|
+
case SwiftDataError.modelValidationFailure:
|
|
955
|
+
// Model fails validation (e.g., unique constraint)
|
|
956
|
+
case SwiftDataError.loadIssueModelContainer:
|
|
957
|
+
// Container could not load the store
|
|
958
|
+
default:
|
|
959
|
+
// Handle other SwiftData errors
|
|
960
|
+
}
|
|
961
|
+
} catch {
|
|
962
|
+
// Handle non-SwiftData errors
|
|
963
|
+
}
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
### Common Error Categories
|
|
967
|
+
|
|
968
|
+
| Category | Errors |
|
|
969
|
+
|----------|--------|
|
|
970
|
+
| Fetch | `.unsupportedPredicate`, `.unsupportedSortDescriptor`, `.unsupportedKeyPath`, `.includePendingChangesWithBatchSize` |
|
|
971
|
+
| Configuration | `.duplicateConfiguration`, `.configurationFileNameContainsInvalidCharacters`, `.configurationSchemaNotFoundInContainerSchema` |
|
|
972
|
+
| Container | `.loadIssueModelContainer` |
|
|
973
|
+
| Context | `.modelValidationFailure`, `.missingModelContext` |
|
|
974
|
+
| Migration | `.backwardMigration`, `.unknownSchema` |
|
|
975
|
+
| History (iOS 18+) | `.historyTokenExpired`, `.invalidTransactionFetchRequest` |
|