@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.1

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.
Files changed (284) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +285 -285
  5. package/package.json +3 -3
  6. package/pipeline/lib/redact.mjs +3 -2
  7. package/pipeline/scripts/_notices.mjs +1 -1
  8. package/pipeline/scripts/gen-skills-index.mjs +13 -1
  9. package/pipeline/scripts/pre-commit-check.sh +4 -0
  10. package/pipeline/skills/.skill-manifest.json +69 -69
  11. package/pipeline/skills/shared/README.md +70 -70
  12. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  13. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  14. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  15. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  16. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  17. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  18. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  19. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  20. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  21. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  22. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  23. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +345 -277
  24. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  25. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +107 -121
  26. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
  27. package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -326
  28. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  29. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  30. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  31. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +335 -360
  32. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  34. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  35. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  36. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  37. package/pipeline/skills/shared/external/authentication/SKILL.md +277 -381
  38. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  39. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -178
  40. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  41. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  42. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  43. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  44. package/pipeline/skills/shared/external/background-processing/SKILL.md +274 -384
  45. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  46. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
  47. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  48. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  49. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  50. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  51. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  52. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  53. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  54. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  55. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  56. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +228 -376
  57. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  58. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  59. package/pipeline/skills/shared/external/core-data/SKILL.md +302 -368
  60. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  61. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  62. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  63. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  64. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  65. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  66. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  67. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  68. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  69. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  70. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  71. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  72. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  73. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  74. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  75. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  76. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  77. package/pipeline/skills/shared/external/device-integrity/SKILL.md +236 -353
  78. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  79. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  80. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  81. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  82. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  83. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  84. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  85. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  86. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  87. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  88. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  89. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  90. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  91. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  92. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  93. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  94. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  95. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  96. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  97. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  98. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  99. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  100. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  101. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  102. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  103. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  104. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  105. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  106. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  107. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  108. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  109. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  111. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  112. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  113. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  114. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +3 -3
  115. package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
  116. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
  117. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
  118. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
  119. package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
  120. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
  121. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
  122. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
  123. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
  124. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
  125. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
  126. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
  127. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
  128. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
  129. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
  130. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -267
  131. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  132. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  133. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  134. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  135. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  136. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  137. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  138. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  139. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  140. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  141. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  142. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  143. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  144. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  145. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  146. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  147. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  148. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  149. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  150. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  151. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  152. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  153. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  154. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  155. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  156. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  157. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  158. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  159. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  160. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  161. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  162. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  163. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  164. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  165. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  166. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  167. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  168. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  169. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  170. package/pipeline/skills/shared/external/skill-creator/template.md +7 -1
  171. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  172. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  173. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  174. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  175. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  176. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  177. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +302 -241
  178. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  179. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  180. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  181. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  182. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  183. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  184. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  185. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  186. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  187. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  188. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  189. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  190. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  191. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  192. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +304 -351
  193. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  194. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  195. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  196. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  197. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  198. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  199. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  200. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  201. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  202. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  203. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  204. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  205. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  206. package/pipeline/skills/shared/external/swift-security/SKILL.md +183 -162
  207. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  208. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  209. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +411 -476
  210. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  211. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  212. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  213. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  214. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  215. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  216. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +375 -491
  217. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  218. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  219. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  220. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +397 -457
  221. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  222. package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
  223. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  224. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
  225. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  226. package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -256
  227. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  228. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  229. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  230. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  231. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  232. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  233. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  234. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  235. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  236. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  237. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  238. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  239. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  240. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  241. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  242. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  243. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  244. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  245. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  246. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  247. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  248. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  249. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  250. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  251. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +201 -168
  252. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  253. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
  254. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  255. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
  256. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  257. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  258. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  259. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  260. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  261. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  262. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  263. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  264. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  265. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  266. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  267. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  268. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  269. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  270. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  271. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  272. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  273. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  274. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  275. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  276. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  277. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  278. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  279. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  280. package/pipeline/skills/shared/external/weatherkit/SKILL.md +160 -315
  281. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  282. package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
  283. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -719
  284. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,976 +1,583 @@
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
- ---
1
+ # SwiftData: advanced topics
9
2
 
10
3
  ## Contents
11
4
 
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)
5
+ - [Custom data stores](#custom-data-stores)
6
+ - [History tracking](#history-tracking)
7
+ - [CloudKit in detail](#cloudkit-in-detail)
8
+ - [Core Data strategies](#core-data-strategies)
9
+ - [Batch work and performance](#batch-work-and-performance)
10
+ - [Complex predicates](#complex-predicates)
11
+ - [Codable structs as composite attributes](#codable-structs-as-composite-attributes)
12
+ - [Model inheritance](#model-inheritance)
13
+ - [Several configurations in one container](#several-configurations-in-one-container)
14
+ - [Undo and redo](#undo-and-redo)
15
+ - [Previews with in-memory stores](#previews-with-in-memory-stores)
16
+ - [Save notifications](#save-notifications)
17
+ - [Errors](#errors)
25
18
 
26
- ## Custom Data Stores (iOS 18+)
19
+ ## Custom data stores
27
20
 
28
- ### DataStore Protocol
21
+ From iOS 18 you can replace the SQLite backend by conforming to `DataStore`:
22
+ a JSON file, a cache, a REST service, anything that can answer fetches and
23
+ accept saves.
29
24
 
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.).
25
+ Shape of a store:
33
26
 
34
27
  ```swift
35
- final class JSONStore: DataStore {
36
- typealias Configuration = JSONStoreConfiguration
28
+ import SwiftData
29
+ import Foundation
30
+
31
+ struct ArchiveStoreConfiguration: DataStoreConfiguration {
32
+ typealias Store = ArchiveStore
33
+ var name: String
34
+ var schema: Schema?
35
+ let fileURL: URL
36
+
37
+ func validate() throws {
38
+ guard fileURL.isFileURL else { throw DataStoreError.unsupportedFeature }
39
+ }
40
+ }
41
+
42
+ final class ArchiveStore: DataStore {
43
+ typealias Configuration = ArchiveStoreConfiguration
37
44
  typealias Snapshot = DefaultSnapshot
38
45
 
39
- let configuration: JSONStoreConfiguration
46
+ let configuration: ArchiveStoreConfiguration
40
47
  let identifier: String
41
48
  let schema: Schema
42
49
 
43
- init(_ configuration: JSONStoreConfiguration,
50
+ init(_ configuration: ArchiveStoreConfiguration,
44
51
  migrationPlan: (any SchemaMigrationPlan.Type)?) throws {
45
52
  self.configuration = configuration
46
53
  self.identifier = configuration.name
47
54
  self.schema = configuration.schema ?? Schema()
48
55
  }
49
56
 
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
57
+ func fetch<T>(_ request: DataStoreFetchRequest<T>) throws
58
+ -> DataStoreFetchResult<T, Snapshot> where T: PersistentModel {
59
+ let snapshots: [DefaultSnapshot] = [] // read from the archive, apply request.descriptor
60
+ return DataStoreFetchResult(descriptor: request.descriptor,
61
+ fetchedSnapshots: snapshots,
62
+ relatedSnapshots: [:])
66
63
  }
67
64
 
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
- [:]
65
+ func save(_ request: DataStoreSaveChangesRequest<Snapshot>) throws
66
+ -> DataStoreSaveChangesResult<Snapshot> {
67
+ // write request.inserted, request.updated, request.deleted
68
+ DataStoreSaveChangesResult(for: identifier,
69
+ remappedIdentifiers: [:],
70
+ snapshotsToReregister: [:])
97
71
  }
98
72
  }
99
73
  ```
100
74
 
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
- ```
75
+ The sketch shows the two central members. The protocol also requires
76
+ `fetchCount`, `fetchIdentifiers`, `erase()`, `initializeState(for: EditingState)`,
77
+ `invalidateState(for:)` and `cachedSnapshots(for:editingState:)`.
121
78
 
122
- ### Using a Custom Store
79
+ Use it like any other configuration:
123
80
 
124
81
  ```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
- )
82
+ let archive = ArchiveStoreConfiguration(name: "Archive", schema: nil, fileURL: archiveURL)
83
+ let archivedNotes = try ModelContainer(for: ArchivedNote.self, configurations: archive)
133
84
  ```
134
85
 
135
- ### Optional Conformances
86
+ Optional extras:
136
87
 
137
- - **`DataStoreBatching`**: Implement `delete(_:)` for batch delete support.
138
- - **`HistoryProviding`**: Implement `fetchHistory(_:)` and `deleteHistory(_:)`
139
- for change tracking.
88
+ - `DataStoreBatching` adds `delete(_:)` for batch deletes.
89
+ - `HistoryProviding` adds `fetchHistory(_:)` and `deleteHistory(_:)`.
140
90
 
141
- ### DataStoreError Cases
91
+ When a request is beyond the store, throw a `DataStoreError`:
92
+ `.invalidPredicate`, `.preferInMemoryFilter` (SwiftData filters in memory
93
+ instead), `.preferInMemorySort`, or `.unsupportedFeature`.
142
94
 
143
- Handle these when implementing custom stores:
95
+ ## History tracking
144
96
 
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 |
97
+ From iOS 18 the default store records a history of transactions, so an app can
98
+ catch up on what changed since it last looked, including changes made by
99
+ widgets and extensions.
151
100
 
152
- ---
101
+ Label your own writes:
153
102
 
154
- ## History Tracking and Change Detection (iOS 18+)
155
-
156
- ### Enable History Tracking
103
+ ```swift
104
+ context.author = "main-app"
105
+ ```
157
106
 
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.
107
+ Mark attributes whose values should survive into deletion records:
161
108
 
162
109
  ```swift
163
110
  @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
- }
111
+ final class Receipt {
112
+ @Attribute(.preserveValueOnDeletion) var number: String
113
+ var amount: Decimal
114
+ init(number: String, amount: Decimal) { self.number = number; self.amount = amount }
174
115
  }
175
-
176
- // Tag context for history attribution
177
- modelContext.author = "mainApp"
178
116
  ```
179
117
 
180
- ### Fetch History Transactions
118
+ Read changes since a saved token. `mirror` stands in for whatever your app does
119
+ with each change (refresh a cache, update a widget, push to a server). The
120
+ `.update` and `.delete` payloads are existentials, so cast them to
121
+ `DefaultHistoryUpdate<Model>` / `DefaultHistoryDelete<Model>` before reading
122
+ `updatedAttributes` or `tombstone`; a tombstone value comes back as
123
+ `(any Sendable)?`:
181
124
 
182
125
  ```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
126
+ func applyChanges(since lastToken: DefaultHistoryToken?,
127
+ in context: ModelContext,
128
+ mirror: HistoryMirror) throws -> DefaultHistoryToken? {
129
+ var descriptor = HistoryDescriptor<DefaultHistoryTransaction>()
130
+ if let lastToken {
131
+ descriptor.predicate = #Predicate { $0.token > lastToken }
189
132
  }
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
133
+ let transactions = try context.fetchHistory(descriptor)
134
+ for transaction in transactions {
135
+ for change in transaction.changes {
136
+ switch change {
137
+ case .insert(let inserted):
138
+ mirror.added(inserted.changedPersistentIdentifier)
139
+ case .update(let updated):
140
+ if let receipt = updated as? DefaultHistoryUpdate<Receipt> {
141
+ mirror.changed(receipt.changedPersistentIdentifier,
142
+ fields: receipt.updatedAttributes)
143
+ }
144
+ case .delete(let deleted):
145
+ if let receipt = deleted as? DefaultHistoryDelete<Receipt> {
146
+ mirror.removed(receipt.changedPersistentIdentifier,
147
+ number: receipt.tombstone[\.number] as? String)
148
+ }
149
+ @unknown default:
150
+ break
215
151
  }
216
152
  }
217
153
  }
218
-
219
- // Save token for next incremental fetch
220
- savedToken = transaction.token
154
+ return transactions.last?.token ?? lastToken
221
155
  }
222
156
  ```
223
157
 
224
- ### Delete Stale History
158
+ Persist the returned token and pass it in next time. Sorting a
159
+ `HistoryDescriptor` by `\.timestamp` needs iOS 26.
225
160
 
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.
161
+ Prune old history so it does not grow forever:
252
162
 
253
163
  ```swift
254
- for transaction in transactions {
255
- if transaction.author == "widget" {
256
- // Handle widget-originated changes
257
- }
258
- }
164
+ let cutoff = Date.now.addingTimeInterval(-90 * 24 * 60 * 60)
165
+ var old = HistoryDescriptor<DefaultHistoryTransaction>()
166
+ old.predicate = #Predicate { $0.timestamp < cutoff }
167
+ try context.deleteHistory(old)
259
168
  ```
260
169
 
261
- ---
262
-
263
- ## CloudKit Integration
170
+ `DefaultHistoryTransaction` carries `author`, `changes`, `storeIdentifier`,
171
+ `timestamp`, `token` (a `DefaultHistoryToken`), `transactionIdentifier`,
172
+ `bundleIdentifier` and `processIdentifier`. Use `author`, `bundleIdentifier`
173
+ and `processIdentifier` to tell app, widget and extension writes apart.
264
174
 
265
- ### Configuration Options
175
+ ## CloudKit in detail
266
176
 
267
- ```swift
268
- // Automatic: uses CloudKit entitlement from the app
269
- let autoConfig = ModelConfiguration(
270
- cloudKitDatabase: .automatic
271
- )
177
+ `cloudKitDatabase:` on `ModelConfiguration` takes:
272
178
 
273
- // Explicit private database
274
- let privateConfig = ModelConfiguration(
275
- cloudKitDatabase: .private("iCloud.com.example.myapp")
276
- )
179
+ - `.automatic`: use the container named in the app's entitlements.
180
+ - `.private("iCloud.example.app")`: a specific private database.
181
+ - `.none`: never sync this store.
277
182
 
278
- // No CloudKit sync
279
- let localConfig = ModelConfiguration(
280
- cloudKitDatabase: .none
281
- )
282
- ```
283
-
284
- ### Setup Requirements
183
+ Setup:
285
184
 
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. Enable Background Modes > Remote notifications.
290
- 5. Use the container identifier in `ModelConfiguration`.
185
+ 1. Add the iCloud capability to the target.
186
+ 2. Check CloudKit; this adds the `com.apple.developer.icloud-services`
187
+ entitlement.
188
+ 3. Pick or create the container identifier.
189
+ 4. Under Background Modes, turn on Remote notifications.
190
+ 5. Name the same container in `ModelConfiguration`.
291
191
 
292
- ### CloudKit-Compatible Model Design
192
+ A model that syncs cleanly:
293
193
 
294
194
  ```swift
295
195
  @Model
296
- class SyncedNote {
297
- // Keep required scalars nonoptional when defaults/initializers support them
298
- var title: String = ""
196
+ final class JournalEntry {
197
+ var headline: String = ""
299
198
  var body: String?
199
+ @Attribute(.allowsCloudEncryption) var mood: String?
200
+ @Attribute(.externalStorage) var sketch: Data?
201
+ var notebook: Notebook?
300
202
 
301
- // Encrypt sensitive fields in CloudKit
302
- @Attribute(.allowsCloudEncryption) var secretContent: String?
303
-
304
- // Store large data externally
305
- @Attribute(.externalStorage) var attachment: Data?
306
-
307
- // Avoid .unique with CloudKit -- CloudKit does not enforce server-side uniqueness
308
- // Use @Attribute(.unique) only for local-only stores
309
-
310
- init(title: String? = nil, body: String? = nil) {
311
- self.title = title
203
+ init(headline: String, body: String? = nil) {
204
+ self.headline = headline
312
205
  self.body = body
313
206
  }
314
207
  }
315
208
  ```
316
209
 
317
- ### CloudKit Limitations
210
+ `headline` stays a non-optional `String` with a default, and the initializer
211
+ takes a non-optional value for it.
318
212
 
319
- - **Unique constraints**: CloudKit does not enforce uniqueness server-side.
320
- Avoid `@Attribute(.unique)` and `#Unique` on CloudKit-synced models. Use
321
- `cloudKitDatabase: .none` for local-only stores that need uniqueness.
322
- - **Relationships**: CloudKit requires optional relationships. Do not make every
323
- scalar optional just for CloudKit; keep required scalars when defaults,
324
- initializers, or migrations provide valid values.
325
- - **Delete rules**: `.deny` is unsupported for CloudKit sync; enforce that
326
- invariant in app logic if needed.
327
- - **Schema changes**: Initialize and verify the development schema in
328
- nonproduction builds, promote it before release, and treat production changes
329
- as additive-only.
213
+ - CloudKit does not enforce uniqueness on the server, so avoid
214
+ `@Attribute(.unique)` and `#Unique` in synced models. A store that needs them
215
+ should use `cloudKitDatabase: .none`.
216
+ - Relationships must be optional. Scalars do not all have to be.
217
+ - `.deny` delete rules are not supported; enforce that invariant in app code.
330
218
 
331
- ### Multiple Stores: Local + Synced
219
+ Keeping part of the data local while the rest syncs:
332
220
 
333
221
  ```swift
334
- let localConfig = ModelConfiguration(
335
- "Local",
336
- schema: Schema([DraftNote.self]),
337
- cloudKitDatabase: .none
338
- )
222
+ let syncedSchema = Schema([JournalEntry.self, Notebook.self])
223
+ let localSchema = Schema([DraftCache.self])
339
224
 
340
- let syncedConfig = ModelConfiguration(
341
- "Synced",
342
- schema: Schema([PublishedNote.self]),
343
- cloudKitDatabase: .private("iCloud.com.example.app")
344
- )
225
+ let synced = ModelConfiguration("Synced", schema: syncedSchema,
226
+ cloudKitDatabase: .private("iCloud.example.journal"))
227
+ let local = ModelConfiguration("Local", schema: localSchema,
228
+ cloudKitDatabase: .none)
345
229
 
346
230
  let container = try ModelContainer(
347
- for: Schema([DraftNote.self, PublishedNote.self]),
348
- configurations: [localConfig, syncedConfig]
231
+ for: Schema([JournalEntry.self, Notebook.self, DraftCache.self]),
232
+ configurations: [synced, local]
349
233
  )
350
234
  ```
351
235
 
352
- ---
353
-
354
- ## Core Data Coexistence and Migration
355
-
356
- ### Three Strategies
357
-
358
- | Strategy | When to Use |
359
- |----------|-------------|
360
- | Pure Core Data | No migration needed; maintain existing stack |
361
- | Full SwiftData | Greenfield app or complete rewrite |
362
- | Coexistence | Gradual migration; both stacks share the same store |
363
-
364
- ### Coexistence Setup
365
-
366
- Both stacks read/write the same SQLite file. Critical requirements:
367
-
368
- 1. **Enable persistent history tracking** on the Core Data side:
369
- ```swift
370
- let description = NSPersistentStoreDescription()
371
- description.setOption(
372
- true as NSNumber,
373
- forKey: NSPersistentHistoryTrackingKey
374
- )
375
- ```
376
-
377
- 2. **Match entity names** between Core Data `.xcdatamodeld` and SwiftData
378
- `@Model` classes.
379
-
380
- 3. **Use different class names** to avoid conflicts:
381
- ```swift
382
- // Core Data side
383
- class CDTrip: NSManagedObject { /* ... */ }
384
-
385
- // SwiftData side
386
- @Model
387
- class Trip { /* entity name "Trip" matches Core Data entity */ }
388
- ```
236
+ ## Core Data strategies
389
237
 
390
- 4. **Point both stacks at the same store URL**.
238
+ Three paths for an app that has Core Data today:
391
239
 
392
- ### Store File Locations
240
+ | Path | When |
241
+ |---|---|
242
+ | Stay on Core Data | The stack works and nothing needs SwiftData |
243
+ | Move fully to SwiftData | New app, or a planned rewrite |
244
+ | Run both on one store | Gradual move, screen by screen |
393
245
 
394
- | Scenario | Location |
395
- |----------|----------|
396
- | Default | Application Support directory |
397
- | App group entitlement | Root of app group container |
398
- | Explicit URL | `ModelConfiguration(url: customURL)` |
246
+ Running both requires:
399
247
 
400
- ### Migration from Core Data to SwiftData
248
+ - Persistent history on the Core Data side:
249
+ set `NSPersistentHistoryTrackingKey` to `true` on the store description
250
+ before loading it.
251
+ - Matching entity names. Keep the Swift class names different so they do not
252
+ collide, and give the Core Data class the same entity name:
253
+ `CDInvoice: NSManagedObject` for the entity `Invoice`, and
254
+ `@Model final class Invoice` on the SwiftData side.
255
+ - Both stacks opening the same store URL.
401
256
 
402
- Step-by-step:
257
+ Where the store lives: by default under Application Support; with an app group,
258
+ at the root of the group container; or anywhere you choose with
259
+ `ModelConfiguration(url:)`.
403
260
 
404
- 1. Define `VersionedSchema` matching the current Core Data model.
405
- 2. Create `@Model` classes with matching entity/attribute names.
406
- 3. Set up `SchemaMigrationPlan` for future changes.
407
- 4. Enable persistent history tracking on Core Data side.
408
- 5. Point both stacks at the same store file.
409
- 6. Gradually move reads to `@Query` / `FetchDescriptor`.
410
- 7. Move writes to `ModelContext` operations.
411
- 8. Remove Core Data stack when migration is complete.
261
+ Moving over, in order:
412
262
 
413
- ---
263
+ 1. Write a `VersionedSchema` that matches the current Core Data model.
264
+ 2. Write `@Model` classes that match each entity.
265
+ 3. Add a `SchemaMigrationPlan`.
266
+ 4. Turn on persistent history in Core Data.
267
+ 5. Open the shared store file from both stacks.
268
+ 6. Move reads to `@Query` and `FetchDescriptor`.
269
+ 7. Move writes to `ModelContext`.
270
+ 8. Remove the Core Data stack.
414
271
 
415
- ## Batch Operations and Performance
416
-
417
- ### Batch Enumeration
418
-
419
- Process large result sets without loading all objects into memory:
420
-
421
- ```swift
422
- try modelContext.enumerate(
423
- FetchDescriptor<Trip>(),
424
- batchSize: 5000,
425
- allowEscapingMutations: false
426
- ) { trip in
427
- trip.isProcessed = true
428
- }
429
- ```
430
-
431
- - `batchSize`: Number of objects loaded per batch (default 5000).
432
- - `allowEscapingMutations`: Set to `true` only if mutations need to persist
433
- beyond the enumeration block.
434
-
435
- ### Batch Delete
436
-
437
- ```swift
438
- try modelContext.delete(
439
- model: Trip.self,
440
- where: #Predicate { $0.isArchived == true },
441
- includeSubclasses: true // iOS 26+ with inheritance
442
- )
443
- ```
444
-
445
- ### Fetching Only Identifiers
446
-
447
- When full objects are not needed (e.g., for counting or cross-actor references):
448
-
449
- ```swift
450
- let ids = try modelContext.fetchIdentifiers(FetchDescriptor<Trip>())
451
- ```
452
-
453
- ### Fetch Count
454
-
455
- ```swift
456
- let count = try modelContext.fetchCount(
457
- FetchDescriptor<Trip>(predicate: #Predicate { $0.isFavorite == true })
458
- )
459
- ```
460
-
461
- ### Partial Property Fetch
462
-
463
- Fetch only specific properties to reduce memory:
464
-
465
- ```swift
466
- var descriptor = FetchDescriptor<Trip>()
467
- descriptor.propertiesToFetch = [\.name, \.startDate]
468
- let trips = try modelContext.fetch(descriptor)
469
- ```
272
+ Details and a migration test: [core-data-coexistence.md](core-data-coexistence.md).
470
273
 
471
- ### Relationship Prefetching
274
+ ## Batch work and performance
472
275
 
473
- Avoid N+1 query problems by prefetching related objects:
276
+ `delete(model:where:includeSubclasses:)` deletes by predicate without loading
277
+ rows. `includeSubclasses` matters once you use model inheritance (iOS 26+).
474
278
 
475
279
  ```swift
476
- var descriptor = FetchDescriptor<Trip>()
477
- descriptor.relationshipKeyPathsForPrefetching = [\.accommodation, \.tags]
478
- let trips = try modelContext.fetch(descriptor)
280
+ try context.delete(model: Invoice.self,
281
+ where: #Predicate { $0.total == 0 },
282
+ includeSubclasses: true)
479
283
  ```
480
284
 
481
- ### Performance Tips
482
-
483
- - Use `fetchLimit` and `fetchOffset` for pagination.
484
- - Use `enumerate` instead of `fetch` for processing large datasets.
485
- - Use `fetchCount` when only the count is needed.
486
- - Use `fetchIdentifiers` when only IDs are needed.
487
- - Use `propertiesToFetch` to limit loaded data.
488
- - Use `@Attribute(.externalStorage)` for large `Data` payloads such as images
489
- and blobs.
490
- - Disable `includePendingChanges` if unsaved data is not needed in results.
491
- - Call `modelContext.save()` periodically during large imports to flush memory.
285
+ Performance habits:
492
286
 
493
- ---
287
+ - Page with `fetchLimit` and `fetchOffset`.
288
+ - Walk large sets with `enumerate`.
289
+ - Count with `fetchCount`; collect IDs with `fetchIdentifiers`.
290
+ - Load only needed columns with `propertiesToFetch`.
291
+ - Keep blobs out of the table with `.externalStorage`.
292
+ - Set `includePendingChanges = false` when unsaved edits do not matter.
293
+ - In long imports, save every few hundred rows.
494
294
 
495
- ## Complex #Predicate Patterns
295
+ ## Complex predicates
496
296
 
497
- ### Nested Collection Predicates
297
+ These assume an `Invoice` model with `lines`, `client`, `discount`, `memo`,
298
+ `issuedAt`, `total` and `budget`. All outside values are bound before the macro.
498
299
 
499
300
  ```swift
500
- // Trips with at least one high-priority tag
501
- #Predicate<Trip> { trip in
502
- trip.tags.contains { tag in
503
- tag.priority > 5
504
- }
505
- }
301
+ let minimum = 3
302
+ let cal = Calendar.current
303
+ let week = cal.dateInterval(of: .weekOfYear, for: .now)
304
+ let startOfWeek = week?.start ?? .distantPast
305
+ let endOfWeek = week?.end ?? .distantFuture
306
+ let needle = "tax"
307
+ let searchNotes = true
308
+ let earliest: Date? = nil
506
309
 
507
- // Trips where all items are packed
508
- #Predicate<Trip> { trip in
509
- trip.packingList.allSatisfy { item in
510
- item.isPacked == true
511
- }
310
+ // Into a to-many relationship
311
+ let hasBigLine = #Predicate<Invoice> { invoice in
312
+ invoice.lines.contains { $0.quantity > minimum }
512
313
  }
513
- ```
514
-
515
- ### Optional Chaining
516
-
517
- ```swift
518
- // Trips with accommodation in a specific city
519
- #Predicate<Trip> { trip in
520
- trip.accommodation?.city == "Paris"
314
+ let allPaid = #Predicate<Invoice> { invoice in
315
+ invoice.lines.allSatisfy { $0.isPaid }
521
316
  }
522
317
 
523
- // Nil coalescing
524
- #Predicate<Trip> { trip in
525
- (trip.accommodation?.rating ?? 0) >= 4
526
- }
527
- ```
318
+ // Optional chaining and nil coalescing
319
+ let clientInBerlin = #Predicate<Invoice> { $0.client?.city == "Berlin" }
320
+ let lowDiscount = #Predicate<Invoice> { ($0.discount ?? 0) < 10 }
528
321
 
529
- ### String Operations
322
+ // Prefix and case-insensitive search
323
+ let draftNumber = #Predicate<Invoice> { $0.number.starts(with: "DRAFT-") }
324
+ let mentionsTax = #Predicate<Invoice> { $0.memo.localizedStandardContains(needle) }
530
325
 
531
- ```swift
532
- // Case-insensitive search
533
- #Predicate<Trip> { trip in
534
- trip.destination.localizedStandardContains(searchText)
535
- }
326
+ // Date window and arithmetic between properties
327
+ let thisWeek = #Predicate<Invoice> { $0.issuedAt >= startOfWeek && $0.issuedAt < endOfWeek }
328
+ let overBudget = #Predicate<Invoice> { $0.total > $0.budget * 1.1 }
536
329
 
537
- // Prefix matching
538
- #Predicate<Trip> { trip in
539
- trip.name.starts(with: "Summer")
330
+ // Ternary chooses the field to search
331
+ let flexible = #Predicate<Invoice> {
332
+ searchNotes ? $0.memo.localizedStandardContains(needle)
333
+ : $0.number.localizedStandardContains(needle)
540
334
  }
541
- ```
542
-
543
- ### Date and Numeric Ranges
544
-
545
- ```swift
546
- let startOfYear = Calendar.current.date(from: DateComponents(year: 2026, month: 1, day: 1))!
547
- let endOfYear = Calendar.current.date(from: DateComponents(year: 2026, month: 12, day: 31))!
548
-
549
- #Predicate<Trip> { trip in
550
- trip.startDate >= startOfYear && trip.startDate <= endOfYear
551
- }
552
-
553
- // Arithmetic
554
- #Predicate<Trip> { trip in
555
- trip.budget - trip.spent > 100.0
556
- }
557
- ```
558
-
559
- ### Ternary Expressions
560
-
561
- ```swift
562
- #Predicate<Trip> { trip in
563
- (trip.isFavorite ? trip.name : trip.destination).localizedStandardContains(searchText)
564
- }
565
- ```
566
335
 
567
- ### Combining Multiple Predicates
568
-
569
- Build predicates incrementally using captured variables:
570
-
571
- ```swift
572
- func buildPredicate(
573
- searchText: String,
574
- onlyFavorites: Bool,
575
- minDate: Date?
576
- ) -> Predicate<Trip> {
577
- #Predicate<Trip> { trip in
578
- (searchText.isEmpty || trip.name.localizedStandardContains(searchText))
579
- && (!onlyFavorites || trip.isFavorite == true)
580
- && (minDate == nil || trip.startDate >= (minDate ?? .distantPast))
581
- }
336
+ // Optional parameter that may or may not filter
337
+ let floor = earliest ?? .distantPast
338
+ let sinceMaybe = #Predicate<Invoice> {
339
+ earliest == nil || $0.issuedAt >= floor
582
340
  }
583
341
  ```
584
342
 
585
- ### Type Casting in Predicates (iOS 26+, with Inheritance)
343
+ With model inheritance (iOS 26+) a predicate can test the concrete type:
586
344
 
587
345
  ```swift
588
- // Filter for business trips only
589
- #Predicate<Trip> { trip in
590
- trip is BusinessTrip
591
- }
346
+ let onlyRecurring = #Predicate<Invoice> { $0 is RecurringInvoice }
592
347
  ```
593
348
 
594
- ---
595
-
596
- ## Composite Attributes (iOS 18+)
349
+ ## Codable structs as composite attributes
597
350
 
598
- Codable structs stored as composite (nested) attributes in the database.
351
+ A `Codable` struct property is stored as a composite attribute
352
+ (`Schema.CompositeAttribute`): its fields become columns in the owning model's
353
+ table rather than a separate table. Optional composites are allowed. Storing
354
+ Codable structs and enums has been supported since SwiftData's first release;
355
+ what varies by OS is how well predicates reach inside them, so test those on
356
+ your oldest deployment target.
599
357
 
600
358
  ```swift
601
- struct Address: Codable {
359
+ struct PostalAddress: Codable, Hashable {
602
360
  var street: String
603
361
  var city: String
604
- var state: String
605
- var zip: String
362
+ var postcode: String
606
363
  }
607
364
 
608
365
  @Model
609
- class Person {
366
+ final class Customer {
610
367
  var name: String
611
- var homeAddress: Address // Stored as composite attribute
612
- var workAddress: Address?
368
+ var billing: PostalAddress
369
+ var shipping: PostalAddress?
613
370
 
614
- init(name: String, homeAddress: Address) {
371
+ init(name: String, billing: PostalAddress) {
615
372
  self.name = name
616
- self.homeAddress = homeAddress
373
+ self.billing = billing
617
374
  }
618
375
  }
619
- ```
620
376
 
621
- Composite attributes appear as `Schema.CompositeAttribute` in the schema.
622
- Sub-properties are stored inline in the same table. Query individual fields
623
- via key-path navigation in `#Predicate`:
624
-
625
- ```swift
626
- #Predicate<Person> { person in
627
- person.homeAddress.city == "San Francisco"
628
- }
377
+ let local = #Predicate<Customer> { $0.billing.city == "Lyon" }
629
378
  ```
630
379
 
631
- ---
632
-
633
- ## Model Inheritance (iOS 26+)
380
+ ## Model inheritance
634
381
 
635
- ### Base and Subclass Pattern
382
+ From iOS 26 a `@Model` class can subclass another. The subclass adds stored
383
+ properties and calls `super.init`.
636
384
 
637
385
  ```swift
386
+ @available(iOS 26, *)
638
387
  @Model
639
- class Trip {
640
- var name: String
641
- var destination: String
642
- var startDate: Date
643
- var endDate: Date
644
-
645
- init(name: String, destination: String, startDate: Date, endDate: Date) {
646
- self.name = name
647
- self.destination = destination
648
- self.startDate = startDate
649
- self.endDate = endDate
650
- }
651
- }
652
-
653
- @Model
654
- class PersonalTrip: Trip {
655
- var companion: String?
388
+ class Invoice {
389
+ var number: String
390
+ var total: Decimal
391
+ init(number: String, total: Decimal) { self.number = number; self.total = total }
656
392
  }
657
393
 
394
+ @available(iOS 26, *)
658
395
  @Model
659
- class BusinessTrip: Trip {
660
- var company: String
661
- var expenseReport: Data?
662
-
663
- init(name: String, destination: String, startDate: Date, endDate: Date,
664
- company: String) {
665
- self.company = company
666
- super.init(name: name, destination: destination,
667
- startDate: startDate, endDate: endDate)
396
+ final class RecurringInvoice: Invoice {
397
+ var intervalMonths: Int
398
+ init(number: String, total: Decimal, intervalMonths: Int) {
399
+ self.intervalMonths = intervalMonths
400
+ super.init(number: number, total: total)
668
401
  }
669
402
  }
670
403
  ```
671
404
 
672
- ### Querying with Inheritance
405
+ - Fetching `Invoice` returns plain invoices and every subclass.
406
+ - Fetching `RecurringInvoice` returns only that subclass.
407
+ - `ModelContainer(for: Invoice.self)` registers the subclasses too.
673
408
 
674
- ```swift
675
- // Fetch all trips (includes PersonalTrip and BusinessTrip)
676
- let allTrips = try modelContext.fetch(FetchDescriptor<Trip>())
409
+ ## Several configurations in one container
677
410
 
678
- // Fetch only business trips
679
- let businessTrips = try modelContext.fetch(FetchDescriptor<BusinessTrip>())
411
+ Separate stores with different sync settings: see the local plus synced
412
+ example in [CloudKit in detail](#cloudkit-in-detail).
680
413
 
681
- // Delete with subclass inclusion
682
- try modelContext.delete(
683
- model: Trip.self,
684
- where: #Predicate { $0.destination == "Cancelled" },
685
- includeSubclasses: true
686
- )
687
- ```
688
-
689
- ### Container Registration
690
-
691
- Register the base class; subclasses are included automatically:
414
+ A read-only seed store shipped in the bundle:
692
415
 
693
416
  ```swift
694
- let container = try ModelContainer(for: Trip.self)
695
- // PersonalTrip and BusinessTrip are included via inheritance
696
- ```
697
-
698
- ---
699
-
700
- ## Multiple ModelContainer Configurations
701
-
702
- ### Separate Stores for Different Data
703
-
704
- ```swift
705
- // Local-only data (no sync)
706
- let localConfig = ModelConfiguration(
707
- "Local",
708
- schema: Schema([AppSettings.self, CacheEntry.self]),
709
- isStoredInMemoryOnly: false,
710
- cloudKitDatabase: .none
711
- )
712
-
713
- // Synced data
714
- let syncConfig = ModelConfiguration(
715
- "Synced",
716
- schema: Schema([UserDocument.self, SharedNote.self]),
717
- cloudKitDatabase: .private("iCloud.com.example.app")
718
- )
719
-
720
- let container = try ModelContainer(
721
- for: Schema([AppSettings.self, CacheEntry.self, UserDocument.self, SharedNote.self]),
722
- configurations: [localConfig, syncConfig]
723
- )
417
+ if let seedURL = Bundle.main.url(forResource: "Species", withExtension: "store") {
418
+ let seed = ModelConfiguration("Seed", schema: Schema([Species.self]),
419
+ url: seedURL, allowsSave: false)
420
+ let user = ModelConfiguration("User", schema: Schema([Sighting.self]))
421
+ let fieldGuide = try ModelContainer(for: Schema([Species.self, Sighting.self]),
422
+ configurations: [seed, user])
423
+ }
724
424
  ```
725
425
 
726
- ### Read-Only Bundled Database
426
+ Sharing with widgets and extensions:
727
427
 
728
428
  ```swift
729
- let bundledURL = Bundle.main.url(forResource: "seed", withExtension: "store")!
730
- let readOnlyConfig = ModelConfiguration(
731
- "SeedData",
732
- schema: Schema([ReferenceItem.self]),
733
- url: bundledURL,
734
- allowsSave: false
735
- )
429
+ let shared = ModelConfiguration(groupContainer: .identifier("group.example.journal"))
736
430
  ```
737
431
 
738
- ### App Group Sharing (Widget / Extension)
739
-
740
- ```swift
741
- let sharedConfig = ModelConfiguration(
742
- groupContainer: .identifier("group.com.example.myapp")
743
- )
744
- let container = try ModelContainer(for: Trip.self, configurations: sharedConfig)
745
- ```
432
+ ## Undo and redo
746
433
 
747
- ---
748
-
749
- ## Undo/Redo Support
750
-
751
- ### Setup
752
-
753
- ```swift
754
- let context = ModelContext(container)
755
- context.undoManager = UndoManager()
756
- ```
757
-
758
- ### SwiftUI Integration
434
+ Give the context an `UndoManager`, for example once at launch:
759
435
 
760
436
  ```swift
761
437
  @main
762
- struct MyApp: App {
438
+ struct LedgerApp: App {
763
439
  let container: ModelContainer
764
440
 
765
441
  init() {
766
442
  do {
767
- container = try ModelContainer(for: Trip.self)
768
- container.mainContext.undoManager = UndoManager()
443
+ container = try ModelContainer(for: Invoice.self)
769
444
  } catch {
770
- fatalError("Failed to create ModelContainer: \(error)")
445
+ fatalError("Store failed to open: \(error)")
771
446
  }
447
+ container.mainContext.undoManager = UndoManager()
772
448
  }
773
449
 
774
450
  var body: some Scene {
775
- WindowGroup {
776
- ContentView()
777
- }
778
- .modelContainer(container)
451
+ WindowGroup { InvoiceList() }.modelContainer(container)
779
452
  }
780
453
  }
781
454
  ```
782
455
 
783
- ### Using Undo/Redo
456
+ Or adopt the window's undo manager from a view:
784
457
 
785
458
  ```swift
786
- struct TripEditorView: View {
787
- @Environment(\.modelContext) private var modelContext
459
+ struct UndoControls: View {
460
+ @Environment(\.modelContext) private var context
788
461
  @Environment(\.undoManager) private var undoManager
789
462
 
790
463
  var body: some View {
791
- VStack {
792
- // ... editing UI ...
793
- }
794
- .toolbar {
795
- ToolbarItemGroup {
796
- Button("Undo") {
797
- modelContext.undoManager?.undo()
798
- }
799
- .disabled(!(modelContext.undoManager?.canUndo ?? false))
800
-
801
- Button("Redo") {
802
- modelContext.undoManager?.redo()
803
- }
804
- .disabled(!(modelContext.undoManager?.canRedo ?? false))
805
- }
806
- }
807
- .onAppear {
808
- modelContext.undoManager = undoManager
464
+ HStack {
465
+ Button("Undo") { context.undoManager?.undo() }
466
+ .disabled(!(context.undoManager?.canUndo ?? false))
467
+ Button("Redo") { context.undoManager?.redo() }
468
+ .disabled(!(context.undoManager?.canRedo ?? false))
809
469
  }
470
+ .onAppear { context.undoManager = undoManager }
810
471
  }
811
472
  }
812
473
  ```
813
474
 
814
- Process pending changes to register undo actions:
475
+ After an insert that must be undoable as its own step, call
476
+ `context.processPendingChanges()` so the undo manager records it.
815
477
 
816
- ```swift
817
- modelContext.insert(trip)
818
- modelContext.processPendingChanges()
819
- // Now undo is available for the insertion
820
- ```
821
-
822
- ---
823
-
824
- ## Preview Patterns with In-Memory Stores
478
+ ## Previews with in-memory stores
825
479
 
826
- ### Basic Preview Container
480
+ One shared container, seeded once:
827
481
 
828
482
  ```swift
829
483
  @MainActor
830
- let previewContainer: ModelContainer = {
831
- let config = ModelConfiguration(isStoredInMemoryOnly: true)
832
- let container = try! ModelContainer(for: Trip.self, configurations: config)
833
-
834
- // Seed sample data
835
- let sampleTrips = [
836
- Trip(name: "Summer in Paris", destination: "Paris",
837
- startDate: .now, endDate: .now.addingTimeInterval(86400 * 7)),
838
- Trip(name: "Tokyo Adventure", destination: "Tokyo",
839
- startDate: .now.addingTimeInterval(86400 * 30),
840
- endDate: .now.addingTimeInterval(86400 * 37)),
841
- ]
842
- for trip in sampleTrips {
843
- container.mainContext.insert(trip)
484
+ let sampleLedger: ModelContainer = {
485
+ do {
486
+ let container = try ModelContainer(
487
+ for: Customer.self, Invoice.self,
488
+ configurations: ModelConfiguration(isStoredInMemoryOnly: true)
489
+ )
490
+ let customer = Customer(name: "Ada",
491
+ billing: PostalAddress(street: "1 Main", city: "Lyon", postcode: "69001"))
492
+ container.mainContext.insert(customer)
493
+ container.mainContext.insert(Invoice(number: "INV-1", total: 120))
494
+ return container
495
+ } catch {
496
+ fatalError("Preview store failed: \(error)")
844
497
  }
845
-
846
- return container
847
498
  }()
848
499
 
849
- #Preview {
850
- TripListView()
851
- .modelContainer(previewContainer)
852
- }
853
- ```
854
-
855
- ### Preview with Relationships
856
-
857
- ```swift
858
- #Preview {
859
- let config = ModelConfiguration(isStoredInMemoryOnly: true)
860
- let container = try! ModelContainer(
861
- for: Trip.self, LivingAccommodation.self,
862
- configurations: config
863
- )
864
-
865
- let trip = Trip(name: "Beach Trip", destination: "Malibu",
866
- startDate: .now, endDate: .now.addingTimeInterval(86400 * 3))
867
- let hotel = LivingAccommodation(name: "Beach Resort")
868
- trip.accommodation = hotel
869
-
870
- container.mainContext.insert(trip)
871
-
872
- return TripDetailView(trip: trip)
873
- .modelContainer(container)
874
- }
500
+ #Preview { InvoiceList().modelContainer(sampleLedger) }
875
501
  ```
876
502
 
877
- ### Preview Trait (iOS 18+)
503
+ With relationships, register every related type, connect the objects, then
504
+ insert the parent; inserting the parent inserts the children with it.
878
505
 
879
- Use `PreviewModifier` for reusable preview configurations:
506
+ From iOS 18, a `PreviewModifier` builds the container once and shares it across
507
+ previews:
880
508
 
881
509
  ```swift
882
- struct SampleDataPreview: PreviewModifier {
883
- static func makeSharedContext() async throws -> ModelContainer {
884
- let config = ModelConfiguration(isStoredInMemoryOnly: true)
885
- let container = try ModelContainer(for: Trip.self, configurations: config)
886
- // Insert sample data
510
+ struct LedgerSampleData: PreviewModifier {
511
+ typealias Context = ModelContainer
512
+
513
+ static func makeSharedContext() async throws -> Context {
514
+ let container = try ModelContainer(
515
+ for: Invoice.self,
516
+ configurations: ModelConfiguration(isStoredInMemoryOnly: true)
517
+ )
518
+ container.mainContext.insert(Invoice(number: "INV-7", total: 42))
887
519
  return container
888
520
  }
889
521
 
890
- func body(content: Content, context: ModelContainer) -> some View {
522
+ func body(content: Content, context: Context) -> some View {
891
523
  content.modelContainer(context)
892
524
  }
893
525
  }
894
526
 
895
- extension PreviewTrait where T == Preview.ViewTraits {
896
- static var sampleData: Self = .modifier(SampleDataPreview())
527
+ extension PreviewTrait<Preview.ViewTraits> {
528
+ @MainActor static var ledgerSample: Self = .modifier(LedgerSampleData())
897
529
  }
898
530
 
899
- #Preview(traits: .sampleData) {
900
- TripListView()
901
- }
531
+ #Preview(traits: .ledgerSample) { InvoiceList() }
902
532
  ```
903
533
 
904
- ---
905
-
906
- ## Notification Observation
534
+ ## Save notifications
907
535
 
908
- ### Observing Save Events
536
+ Observe `ModelContext.didSave` for a specific context:
909
537
 
910
538
  ```swift
911
- NotificationCenter.default.publisher(for: ModelContext.didSave, object: modelContext)
912
- .sink { notification in
913
- if let insertedIDs = notification.userInfo?[
914
- ModelContext.NotificationKey.insertedIdentifiers
915
- ] as? Set<PersistentIdentifier> {
916
- // Handle new insertions
917
- }
918
-
919
- if let updatedIDs = notification.userInfo?[
920
- ModelContext.NotificationKey.updatedIdentifiers
921
- ] as? Set<PersistentIdentifier> {
922
- // Handle updates
923
- }
924
-
925
- if let deletedIDs = notification.userInfo?[
926
- ModelContext.NotificationKey.deletedIdentifiers
927
- ] as? Set<PersistentIdentifier> {
928
- // Handle deletions
929
- }
930
- }
539
+ let token = NotificationCenter.default.addObserver(
540
+ forName: ModelContext.didSave, object: context, queue: .main
541
+ ) { note in
542
+ let inserted = note.userInfo?[ModelContext.NotificationKey.insertedIdentifiers.rawValue]
543
+ as? Set<PersistentIdentifier> ?? []
544
+ refreshBadges(for: inserted)
545
+ }
931
546
  ```
932
547
 
933
- ### Available Notification Keys
934
-
935
- | Key | Description |
936
- |-----|-------------|
937
- | `.insertedIdentifiers` | IDs of newly inserted models |
938
- | `.updatedIdentifiers` | IDs of updated models |
939
- | `.deletedIdentifiers` | IDs of deleted models |
940
- | `.invalidatedAllIdentifiers` | All data invalidated (e.g., store reset) |
941
- | `.queryGeneration` | Query generation token |
942
-
943
- ---
548
+ Keys under `ModelContext.NotificationKey`: `.insertedIdentifiers`,
549
+ `.updatedIdentifiers` and `.deletedIdentifiers` (each a
550
+ `Set<PersistentIdentifier>`), plus `.invalidatedAllIdentifiers` and
551
+ `.queryGeneration`.
944
552
 
945
- ## Error Handling
553
+ ## Errors
946
554
 
947
- ### SwiftDataError Cases
555
+ Catch `SwiftDataError` and compare against its static values:
948
556
 
949
557
  ```swift
950
558
  do {
951
- let trips = try modelContext.fetch(descriptor)
559
+ try context.save()
952
560
  } catch let error as SwiftDataError {
953
561
  switch error {
954
- case SwiftDataError.unsupportedPredicate:
955
- // Predicate uses unsupported operations
956
- case SwiftDataError.unsupportedSortDescriptor:
957
- // Sort descriptor cannot be processed
958
- case SwiftDataError.modelValidationFailure:
959
- // Model fails validation (e.g., unique constraint)
960
- case SwiftDataError.loadIssueModelContainer:
961
- // Container could not load the store
562
+ case .modelValidationFailure:
563
+ // for example a uniqueness conflict
564
+ break
565
+ case .unsupportedPredicate, .unsupportedSortDescriptor:
566
+ break
567
+ case .loadIssueModelContainer:
568
+ break
962
569
  default:
963
- // Handle other SwiftData errors
570
+ break
964
571
  }
965
572
  } catch {
966
- // Handle non-SwiftData errors
573
+ // not a SwiftDataError; without this clause the do-catch is not exhaustive
967
574
  }
968
575
  ```
969
576
 
970
- ### Common Error Categories
577
+ Grouped by where they come from:
971
578
 
972
- | Category | Errors |
973
- |----------|--------|
579
+ | Area | Errors |
580
+ |---|---|
974
581
  | Fetch | `.unsupportedPredicate`, `.unsupportedSortDescriptor`, `.unsupportedKeyPath`, `.includePendingChangesWithBatchSize` |
975
582
  | Configuration | `.duplicateConfiguration`, `.configurationFileNameContainsInvalidCharacters`, `.configurationSchemaNotFoundInContainerSchema` |
976
583
  | Container | `.loadIssueModelContainer` |