@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +266 -267
  5. package/package.json +2 -2
  6. package/pipeline/scripts/_notices.mjs +1 -1
  7. package/pipeline/skills/.skill-manifest.json +68 -68
  8. package/pipeline/skills/shared/README.md +70 -70
  9. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  10. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  11. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  12. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  13. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  14. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  15. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  16. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  17. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  18. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  19. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  20. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  21. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  22. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  23. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  24. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  25. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  26. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  27. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  28. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  29. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  30. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  31. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  32. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  34. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  35. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  36. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  37. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  38. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  39. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  40. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  41. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  42. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  43. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  44. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  45. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  46. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  47. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  48. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  49. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  50. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  51. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  52. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  53. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  54. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  55. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  56. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  57. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  58. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  59. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  60. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  61. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  62. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  63. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  64. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  65. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  66. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  67. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  68. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  69. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  70. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  71. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  72. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  73. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  74. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  75. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  76. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  77. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  78. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  79. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  80. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  81. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  82. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  83. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  84. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  85. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  86. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  87. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  88. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  89. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  90. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  91. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  92. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  93. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  94. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  95. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  96. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  97. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  98. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  99. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  100. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  101. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  102. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  103. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  104. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  105. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  106. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  107. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  108. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  109. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  111. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  112. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  113. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  114. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  115. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  116. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  117. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  118. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  119. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  120. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  121. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  122. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  123. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  124. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  125. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  126. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  127. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  128. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  129. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  130. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  131. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  132. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  133. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  134. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  135. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  136. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  137. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  138. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  139. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  140. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  141. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  142. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  143. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  144. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  145. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  146. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  147. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  148. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  149. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  150. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  151. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  152. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  153. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  154. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  155. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  156. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  157. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  158. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  159. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  160. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  161. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  162. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  163. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  164. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  165. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  166. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  167. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  168. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  169. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  170. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  171. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  172. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  173. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  174. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  175. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  176. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  177. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  178. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  179. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  180. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  181. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  182. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  183. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  184. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  185. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  186. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  187. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  188. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  189. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  190. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  191. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  192. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  193. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  194. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  195. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  196. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  197. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  198. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  199. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  200. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  201. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  202. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  203. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  204. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  205. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  206. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  207. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  208. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  209. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  210. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  211. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  212. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  213. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  214. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  215. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  216. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  217. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  218. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  219. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  220. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  221. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  222. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  223. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  224. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  225. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  226. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  227. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  228. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  229. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  230. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  231. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  232. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  233. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  234. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  235. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  236. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  237. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  238. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  239. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  240. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  241. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  242. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  243. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  244. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  245. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  246. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  247. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  248. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  249. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  250. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  251. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  252. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  253. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  254. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  255. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  256. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  257. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  258. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  259. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  260. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  261. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  262. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  263. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  264. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,683 +1,529 @@
1
- # HealthKit Extended Patterns
1
+ # HealthKit patterns
2
2
 
3
- Overflow reference for the `healthkit` skill. Contains advanced patterns that exceed the main skill file's scope.
3
+ Extended examples that go with the main skill. Swift 6.3, iOS 26 unless noted.
4
4
 
5
5
  ## Contents
6
6
 
7
- - [Workout Session Lifecycle](#workout-session-lifecycle)
8
- - [Platform and Authorization Edge Cases](#platform-and-authorization-edge-cases)
9
- - [Background Delivery Details](#background-delivery-details)
10
- - [Anchored Object Queries](#anchored-object-queries)
11
- - [Predicate-Based Filtering](#predicate-based-filtering)
12
- - [Statistics Collection for Charts](#statistics-collection-for-charts)
13
- - [HealthKit + SwiftUI Integration](#healthkit--swiftui-integration)
14
- - [Characteristic Types](#characteristic-types)
7
+ - [Workout sessions in depth](#workout-sessions-in-depth)
8
+ - [Platform and authorization edge cases](#platform-and-authorization-edge-cases)
9
+ - [Background delivery in detail](#background-delivery-in-detail)
10
+ - [Anchored object queries](#anchored-object-queries)
11
+ - [Predicates](#predicates)
12
+ - [Chart data](#chart-data)
13
+ - [SwiftUI integration](#swiftui-integration)
14
+ - [Characteristics](#characteristics)
15
15
 
16
- ## Workout Session Lifecycle
16
+ ## Workout sessions in depth
17
17
 
18
- `HKWorkoutSession` is available on iOS/iPadOS 17+, visionOS 1+, and watchOS
19
- 2+. `HKLiveWorkoutBuilder` is available on iOS/iPadOS 26+ and watchOS 5+.
20
- When supporting iOS or iPadOS earlier than 26, gate live-builder code and use a
21
- non-live workout save path where appropriate.
18
+ Availability repeats here because it drives the design: `HKWorkoutSession` is
19
+ iOS and iPadOS 17+, visionOS 1+ and watchOS 2+, while `HKLiveWorkoutBuilder`
20
+ is iOS and iPadOS 26+ and watchOS 5+. If the app still supports iOS or iPadOS
21
+ before 26, gate the live builder and fall back to building and saving the
22
+ workout after the fact instead of collecting it live.
22
23
 
23
- iPhone and iPad do not provide built-in live heart-rate samples. They require a
24
- paired external heart-rate sensor for live heart-rate collection, while Apple
25
- Watch workout sessions collect high-frequency heart-rate samples. iPhone also
26
- often locks during workouts; if the app shows health metrics on the Lock
27
- Screen, design around the system's workout-data access prompt and Live Activity
28
- surface.
24
+ iPhone and iPad produce no live heart-rate samples by themselves; an external
25
+ Bluetooth heart-rate sensor is needed. Phones also tend to lock partway through
26
+ a workout, so if metrics should show on the Lock Screen, design around the
27
+ system prompt that grants workout-data access there and around the Live
28
+ Activity that displays it.
29
29
 
30
- ### Full Workout Manager
30
+ ### A complete manager
31
31
 
32
32
  ```swift
33
33
  import HealthKit
34
+ import Observation
34
35
 
36
+ @available(iOS 26.0, *)
35
37
  @Observable
36
38
  @MainActor
37
- final class WorkoutManager: NSObject {
38
- let healthStore = HKHealthStore()
39
-
39
+ final class SwimTracker: NSObject {
40
+ private let store: HKHealthStore
40
41
  private var session: HKWorkoutSession?
41
42
  private var builder: HKLiveWorkoutBuilder?
42
43
 
43
- var heartRate: Double = 0
44
- var activeCalories: Double = 0
45
- var distance: Double = 0
46
- var elapsedTime: TimeInterval = 0
47
- var isActive = false
48
-
49
- func startWorkout(activityType: HKWorkoutActivityType) async throws {
50
- let configuration = HKWorkoutConfiguration()
51
- configuration.activityType = activityType
52
- configuration.locationType = .outdoor
53
-
54
- let session = try HKWorkoutSession(
55
- healthStore: healthStore,
56
- configuration: configuration
57
- )
58
- let builder = session.associatedWorkoutBuilder()
59
-
60
- builder.dataSource = HKLiveWorkoutDataSource(
61
- healthStore: healthStore,
62
- workoutConfiguration: configuration
63
- )
64
-
65
- session.delegate = self
66
- builder.delegate = self
67
-
68
- self.session = session
69
- self.builder = builder
44
+ private(set) var pulse: Double = 0
45
+ private(set) var kilocalories: Double = 0
46
+ private(set) var metres: Double = 0
47
+ private(set) var elapsed: TimeInterval = 0
48
+ private(set) var isRunning = false
70
49
 
71
- session.startActivity(with: Date())
72
- try await builder.beginCollection(at: Date())
73
- isActive = true
50
+ init(store: HKHealthStore) {
51
+ self.store = store
74
52
  }
75
53
 
76
- func pause() {
77
- session?.pause()
54
+ func start() async throws {
55
+ let plan = HKWorkoutConfiguration()
56
+ plan.activityType = .swimming
57
+ plan.swimmingLocationType = .pool
58
+ plan.lapLength = HKQuantity(unit: .meter(), doubleValue: 25)
59
+
60
+ let newSession = try HKWorkoutSession(healthStore: store, configuration: plan)
61
+ let newBuilder = newSession.associatedWorkoutBuilder()
62
+ newSession.delegate = self
63
+ newBuilder.delegate = self
64
+ newBuilder.dataSource = HKLiveWorkoutDataSource(healthStore: store, workoutConfiguration: plan)
65
+ session = newSession
66
+ builder = newBuilder
67
+
68
+ let startDate = Date()
69
+ newSession.startActivity(with: startDate)
70
+ try await newBuilder.beginCollection(at: startDate)
78
71
  }
79
72
 
80
- func resume() {
81
- session?.resume()
73
+ func pause() { session?.pause() }
74
+ func resume() { session?.resume() }
75
+
76
+ func stop() async throws {
77
+ guard let activeSession = session, let activeBuilder = builder else { return }
78
+ activeSession.end()
79
+ try await activeBuilder.endCollection(at: .now)
80
+ _ = try await activeBuilder.finishWorkout()
81
+ session = nil
82
+ builder = nil
82
83
  }
83
84
 
84
- func end() async throws {
85
- guard let session, let builder else { return }
86
- session.end()
87
- try await builder.endCollection(at: Date())
88
- try await builder.finishWorkout()
89
- isActive = false
90
- self.session = nil
91
- self.builder = nil
85
+ fileprivate func apply(_ stats: HKStatistics, for type: HKQuantityType) {
86
+ switch type {
87
+ case HKQuantityType(.heartRate):
88
+ pulse = stats.mostRecentQuantity()?.doubleValue(for: .count().unitDivided(by: .minute())) ?? pulse
89
+ case HKQuantityType(.activeEnergyBurned):
90
+ kilocalories = stats.sumQuantity()?.doubleValue(for: .kilocalorie()) ?? kilocalories
91
+ case HKQuantityType(.distanceSwimming):
92
+ metres = stats.sumQuantity()?.doubleValue(for: .meter()) ?? metres
93
+ default:
94
+ break
95
+ }
92
96
  }
93
97
  }
94
98
 
95
- // MARK: - HKWorkoutSessionDelegate
96
-
97
- extension WorkoutManager: HKWorkoutSessionDelegate {
98
- nonisolated func workoutSession(
99
- _ workoutSession: HKWorkoutSession,
100
- didChangeTo toState: HKWorkoutSessionState,
101
- from fromState: HKWorkoutSessionState,
102
- date: Date
103
- ) {
99
+ @available(iOS 26.0, *)
100
+ extension SwimTracker: HKWorkoutSessionDelegate {
101
+ nonisolated func workoutSession(_ sender: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState,
102
+ from fromState: HKWorkoutSessionState, date: Date) {
104
103
  Task { @MainActor in
105
104
  switch toState {
106
- case .running:
107
- isActive = true
108
- case .paused:
109
- isActive = false
110
- case .ended, .stopped:
111
- isActive = false
112
- default:
113
- break
105
+ case .running: self.isRunning = true
106
+ case .paused, .ended, .stopped: self.isRunning = false
107
+ default: break
114
108
  }
115
109
  }
116
110
  }
117
111
 
118
- nonisolated func workoutSession(
119
- _ workoutSession: HKWorkoutSession,
120
- didFailWithError error: Error
121
- ) {
122
- Task { @MainActor in
123
- print("Workout session failed: \(error)")
124
- isActive = false
125
- }
112
+ nonisolated func workoutSession(_ sender: HKWorkoutSession, didFailWithError failure: Error) {
113
+ Task { @MainActor in self.isRunning = false }
126
114
  }
127
115
  }
128
116
 
129
- // MARK: - HKLiveWorkoutBuilderDelegate
130
-
131
- extension WorkoutManager: HKLiveWorkoutBuilderDelegate {
132
- nonisolated func workoutBuilderDidCollectEvent(
133
- _ workoutBuilder: HKLiveWorkoutBuilder
134
- ) {
135
- // Handle workout events (pause, resume, lap, etc.)
117
+ @available(iOS 26.0, *)
118
+ extension SwimTracker: HKLiveWorkoutBuilderDelegate {
119
+ nonisolated func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
120
+ // Pause, resume and lap markers arrive here via workoutBuilder.workoutEvents.
136
121
  }
137
122
 
138
- nonisolated func workoutBuilder(
139
- _ workoutBuilder: HKLiveWorkoutBuilder,
140
- didCollectDataOf collectedTypes: Set<HKSampleType>
141
- ) {
123
+ nonisolated func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {
124
+ let time = workoutBuilder.elapsedTime
125
+ let snapshot = collectedTypes.compactMap { $0 as? HKQuantityType }
126
+ .compactMap { type in workoutBuilder.statistics(for: type).map { (type, $0) } }
142
127
  Task { @MainActor in
143
- for type in collectedTypes {
144
- guard let quantityType = type as? HKQuantityType else { continue }
128
+ self.elapsed = time
129
+ for (type, stats) in snapshot { self.apply(stats, for: type) }
130
+ }
131
+ }
132
+ }
133
+ ```
145
134
 
146
- let statistics = workoutBuilder.statistics(for: quantityType)
135
+ Points to notice:
147
136
 
148
- switch quantityType {
149
- case HKQuantityType(.heartRate):
150
- heartRate = statistics?.mostRecentQuantity()?
151
- .doubleValue(for: HKUnit.count().unitDivided(by: .minute())) ?? 0
137
+ - Both delegates are set: the session's for state changes and the builder's for
138
+ data.
139
+ - `pause()` and `resume()` go through the session.
140
+ - Ending runs `end()`, `endCollection(at:)`, `finishWorkout()` and then drops
141
+ the references.
142
+ - Delegate callbacks are `nonisolated` and hop back with
143
+ `Task { @MainActor in ... }`. Session states to handle include `.running`,
144
+ `.paused`, `.ended` and `.stopped`.
145
+ - In `didCollectDataOf`, cast each type to `HKQuantityType` and read
146
+ `statistics(for:)`: `mostRecentQuantity()` for heart rate in beats per
147
+ minute, `sumQuantity()` for energy in kilocalories and for distance in
148
+ metres. `elapsedTime` on the builder is the running clock.
152
149
 
153
- case HKQuantityType(.activeEnergyBurned):
154
- activeCalories = statistics?.sumQuantity()?
155
- .doubleValue(for: .kilocalorie()) ?? 0
150
+ ### Mirroring a watch workout to iPhone
156
151
 
157
- case HKQuantityType(.distanceWalkingRunning):
158
- distance = statistics?.sumQuantity()?
159
- .doubleValue(for: .meter()) ?? 0
152
+ The watch runs the primary session and starts mirroring:
160
153
 
161
- default:
162
- break
163
- }
164
- }
154
+ ```swift
155
+ import HealthKit
165
156
 
166
- elapsedTime = workoutBuilder.elapsedTime
167
- }
168
- }
157
+ #if os(watchOS)
158
+ func mirrorToPhone(_ primary: HKWorkoutSession) async throws {
159
+ try await primary.startMirroringToCompanionDevice()
169
160
  }
161
+ #endif
170
162
  ```
171
163
 
172
- ### Multi-Device Mirroring (watchOS + iOS)
173
-
174
- Start mirroring from the primary watchOS session. In the iOS companion app,
175
- assign `workoutSessionMirroringStartHandler` as the app launches so it can
176
- receive mirrored sessions even when launched in the background. The handler runs
177
- on an arbitrary background queue and may be called more than once if the devices
178
- disconnect and reconnect during a workout.
164
+ The iPhone app registers its handler at launch, early enough that a mirrored
165
+ session still arrives when the system launches the app in the background:
179
166
 
180
167
  ```swift
181
- // On watchOS: start mirroring to companion iPhone
182
- func startMirroring() async throws {
183
- try await session?.startMirroringToCompanionDevice()
184
- }
168
+ import HealthKit
185
169
 
186
- // On iOS: receive the mirrored session
187
- func setupMirroredSessionHandler() {
188
- healthStore.workoutSessionMirroringStartHandler = { mirroredSession in
189
- // mirroredSession is an HKWorkoutSession with type == .mirrored
190
- mirroredSession.delegate = self
191
- let builder = mirroredSession.associatedWorkoutBuilder()
192
- builder.delegate = self
170
+ @available(iOS 26.0, *)
171
+ final class CompanionReceiver: NSObject, Sendable, HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate {
172
+ let store = HKHealthStore()
173
+
174
+ func install() {
175
+ store.workoutSessionMirroringStartHandler = { [weak self] mirrored in
176
+ guard let self, mirrored.type == .mirrored else { return }
177
+ mirrored.delegate = self
178
+ mirrored.associatedWorkoutBuilder().delegate = self
179
+ }
193
180
  }
194
- }
195
181
 
196
- // Send data between devices
197
- func sendDataToRemote(_ data: Data) async throws {
198
- try await session?.sendToRemoteWorkoutSession(data: data)
182
+ func ping(_ session: HKWorkoutSession, payload: Data) async throws {
183
+ try await session.sendToRemoteWorkoutSession(data: payload)
184
+ }
185
+
186
+ func workoutSession(_ s: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState,
187
+ from fromState: HKWorkoutSessionState, date: Date) {}
188
+ func workoutSession(_ s: HKWorkoutSession, didFailWithError failure: Error) {}
189
+ func workoutSession(_ s: HKWorkoutSession, didReceiveDataFromRemoteWorkoutSession data: [Data]) {}
190
+ func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {}
191
+ func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {}
199
192
  }
200
193
  ```
201
194
 
202
- ## Platform and Authorization Edge Cases
203
-
204
- - Call `HKHealthStore.isHealthDataAvailable()` before any other HealthKit API.
205
- Current Apple docs say Health data is available on iOS, watchOS, visionOS,
206
- iPadOS 17+, and iOS apps running on Vision Pro. It is unavailable on iPadOS
207
- 16 or earlier and may be restricted by managed device policy.
208
- - Enabling the HealthKit capability for an iOS app can add `healthkit` to
209
- `UIRequiredDeviceCapabilities`, preventing installation on unsupported
210
- devices. Remove that entry only when HealthKit is optional and the app has a
211
- useful non-HealthKit mode.
212
- - `authorizationStatus(for:)` is for write/share authorization. HealthKit does
213
- not reveal whether read access was granted or denied. If read access is
214
- denied, queries return samples your app saved successfully, which can look
215
- like partial or empty data.
216
- - People can change HealthKit permissions later in Settings or the Health app.
217
- Refresh permission-sensitive UI and write paths instead of assuming the
218
- original authorization outcome still applies.
219
- - In Vision Pro Guest User sessions, previously authorized data may be readable,
220
- but new authorization and writes can fail. Treat HealthKit writes as
221
- best-effort unless the user explicitly initiated a save action that needs an
222
- explanation.
223
-
224
- ## Background Delivery Details
225
-
226
- Background delivery requires the HealthKit Background Delivery capability
227
- (`com.apple.developer.healthkit.background-delivery` on current Apple docs) and
228
- an executed `HKObserverQuery` for the same sample type. Set up observer queries
229
- when the app launches, then call `enableBackgroundDelivery` once; the system
230
- persists the registration.
195
+ - The handler is called on an arbitrary background queue.
196
+ - It can fire more than once, for example after the devices disconnect and
197
+ reconnect, so make it idempotent.
198
+ - The session it receives is an ordinary `HKWorkoutSession` whose `type` is
199
+ `.mirrored`. Set the session delegate and the delegate of its
200
+ `associatedWorkoutBuilder()`.
201
+ - Either side sends custom data with `sendToRemoteWorkoutSession(data:)`.
202
+
203
+ ## Platform and authorization edge cases
204
+
205
+ - Turning on the capability can add `healthkit` to
206
+ `UIRequiredDeviceCapabilities`, which stops the app from installing on
207
+ devices without Health. Remove the entry only when HealthKit really is
208
+ optional and the app offers something useful without it.
209
+ - People change permissions later in Settings or in the Health app. Re-check
210
+ before permission-dependent UI and write paths instead of trusting the answer
211
+ from the first request.
212
+ - In a Guest User session on Apple Vision Pro, data the owner already allowed
213
+ may be readable while new authorization requests and writes fail. Treat
214
+ writes as best-effort there, unless the user explicitly asked to save, in
215
+ which case explain what happened.
216
+
217
+ ## Background delivery in detail
218
+
219
+ - Entitlement: `com.apple.developer.healthkit.background-delivery`, added by
220
+ the HealthKit Background Delivery capability option.
221
+ - An executed `HKObserverQuery` for the same sample type must exist.
222
+ - `HKUpdateFrequency` is an upper bound, not a promise.
223
+ - On iOS, step count is delivered at most hourly even if `.immediate` is
224
+ requested.
225
+ - Background server queries do not run in Simulator.
226
+ - If the completion handler is not called, HealthKit backs off and, after
227
+ repeated misses, may stop delivering.
231
228
 
232
229
  ```swift
233
- func configureHealthKitBackgroundDelivery() async throws {
234
- let stepType = HKQuantityType(.stepCount)
230
+ import HealthKit
235
231
 
236
- let query = HKObserverQuery(
237
- sampleType: stepType,
238
- predicate: nil
239
- ) { _, completionHandler, error in
232
+ func startSleepSync(store: HKHealthStore, sync: @escaping @Sendable () async -> Void) async throws {
233
+ let sleep = HKCategoryType(.sleepAnalysis)
234
+ let observer = HKObserverQuery(sampleType: sleep, predicate: nil) { _, completionHandler, error in
240
235
  defer { completionHandler() }
241
- guard error == nil else { return }
242
- Task {
243
- // Run an anchored query or statistics refresh here.
244
- }
236
+ if error != nil { return }
237
+ Task { await sync() }
245
238
  }
246
-
247
- healthStore.execute(query)
248
-
249
- try await healthStore.enableBackgroundDelivery(
250
- for: stepType,
251
- frequency: .hourly
252
- )
239
+ store.execute(observer)
240
+ try await store.enableBackgroundDelivery(for: sleep, frequency: .hourly)
253
241
  }
254
242
  ```
255
243
 
256
- Treat `HKUpdateFrequency` as a maximum delivery rate, not a guarantee. Some
257
- types have tighter system caps; for example, iOS step-count background delivery
258
- is capped at hourly even if `.immediate` is requested. Background server queries
259
- are not supported on Simulator, so validate delivery on real hardware. If the
260
- observer completion handler is not called, HealthKit backs off and can stop
261
- sending background updates after repeated failures.
244
+ The `sync` closure is where an anchored query or a statistics refresh runs.
262
245
 
263
- ## Anchored Object Queries
246
+ ## Anchored object queries
264
247
 
265
- Use `HKAnchoredObjectQuery` for incremental updates -- only fetches samples added or deleted since the last anchor.
248
+ `HKAnchoredObjectQuery` returns only what changed (added or deleted) since the
249
+ last `HKQueryAnchor`.
266
250
 
267
251
  ```swift
268
- @Observable
269
- final class StepTracker {
270
- private let healthStore = HKHealthStore()
271
- private var anchor: HKQueryAnchor?
272
- private var observerQuery: HKObserverQuery?
252
+ import HealthKit
253
+ import Synchronization
273
254
 
274
- var totalSteps: Double = 0
255
+ final class GlucoseFeed: Sendable {
256
+ private let store: HKHealthStore
257
+ private let anchor = Mutex<HKQueryAnchor?>(nil)
275
258
 
276
- func startMonitoring() {
277
- let stepType = HKQuantityType(.stepCount)
259
+ init(store: HKHealthStore) { self.store = store }
278
260
 
279
- // Initial fetch + ongoing updates
280
- let anchoredQuery = HKAnchoredObjectQuery(
281
- type: stepType,
261
+ func start(onChange: @escaping @Sendable ([HKSample], [HKDeletedObject]) -> Void) {
262
+ let query = HKAnchoredObjectQuery(
263
+ type: HKQuantityType(.bloodGlucose),
282
264
  predicate: nil,
283
- anchor: anchor,
265
+ anchor: anchor.withLock { $0 },
284
266
  limit: HKObjectQueryNoLimit
285
- ) { [weak self] query, added, deleted, newAnchor, error in
286
- guard let self else { return }
287
- self.anchor = newAnchor
288
- self.processNewSamples(added ?? [])
267
+ ) { [weak self] _, added, removed, nextAnchor, _ in
268
+ self?.anchor.withLock { $0 = nextAnchor }
269
+ onChange(added ?? [], removed ?? [])
289
270
  }
290
-
291
- // Enable updates handler for real-time monitoring
292
- anchoredQuery.updateHandler = { [weak self] query, added, deleted, newAnchor, error in
293
- guard let self else { return }
294
- self.anchor = newAnchor
295
- self.processNewSamples(added ?? [])
296
- }
297
-
298
- healthStore.execute(anchoredQuery)
299
- }
300
-
301
- private func processNewSamples(_ samples: [HKSample]) {
302
- for sample in samples {
303
- guard let quantitySample = sample as? HKQuantitySample else { continue }
304
- let steps = quantitySample.quantity.doubleValue(for: .count())
305
- totalSteps += steps
271
+ query.updateHandler = { [weak self] _, added, removed, nextAnchor, _ in
272
+ self?.anchor.withLock { $0 = nextAnchor }
273
+ onChange(added ?? [], removed ?? [])
306
274
  }
275
+ store.execute(query)
307
276
  }
308
277
  }
309
- ```
310
278
 
311
- ### Anchored Query with Async Descriptor
312
-
313
- ```swift
314
- import HealthKit
315
-
316
- let stepType = HKQuantityType(.stepCount)
317
- let descriptor = HKAnchoredObjectQueryDescriptor(
318
- predicates: [.quantitySample(type: stepType)],
319
- anchor: savedAnchor
320
- )
321
-
322
- // One-shot
323
- let result = try await descriptor.result(for: healthStore)
324
- let newSamples = result.addedSamples
325
- let deletedObjects = result.deletedObjects
326
- let newAnchor = result.newAnchor
327
-
328
- // Long-running with updates
329
- for try await result in descriptor.results(for: healthStore) {
330
- // Process result.addedSamples and result.deletedObjects
279
+ func glucoseChanges(since mark: HKQueryAnchor?, store: HKHealthStore) async throws -> HKQueryAnchor {
280
+ let query = HKAnchoredObjectQueryDescriptor(
281
+ predicates: [.quantitySample(type: HKQuantityType(.bloodGlucose))],
282
+ anchor: mark
283
+ )
284
+ let delta = try await query.result(for: store)
285
+ _ = (delta.addedSamples, delta.deletedObjects)
286
+ return delta.newAnchor
331
287
  }
332
288
  ```
333
289
 
334
- ## Predicate-Based Filtering
290
+ The first handler delivers the initial batch; `updateHandler` keeps delivering
291
+ changes. Both run on a background queue, so the saved anchor sits behind a
292
+ `Mutex`. With the descriptor, `result(for:)` returns `addedSamples`,
293
+ `deletedObjects` and `newAnchor`, and `results(for:)` streams further updates.
335
294
 
336
- ### Time-Based Predicates
295
+ ## Predicates
337
296
 
338
297
  ```swift
339
- // Samples from today
340
- let today = Calendar.current.startOfDay(for: Date())
341
- let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: today)!
342
- let todayPredicate = HKQuery.predicateForSamples(
343
- withStart: today, end: tomorrow
344
- )
345
-
346
- // Samples from the last 7 days
347
- let oneWeekAgo = Calendar.current.date(byAdding: .day, value: -7, to: Date())!
348
- let weekPredicate = HKQuery.predicateForSamples(
349
- withStart: oneWeekAgo, end: Date()
350
- )
351
-
352
- // Strict: sample must be entirely within the range
353
- let strictPredicate = HKQuery.predicateForSamples(
354
- withStart: today, end: tomorrow,
355
- options: .strictStartDate
356
- )
357
- ```
298
+ import Foundation
299
+ import HealthKit
358
300
 
359
- ### Source-Based Predicates
301
+ func predicates() -> NSPredicate? {
302
+ let now = Date()
303
+ let calendar = Calendar.current
304
+ let todayStart = calendar.startOfDay(for: now)
305
+ guard let weekAgo = calendar.date(byAdding: .day, value: -7, to: now) else { return nil }
360
306
 
361
- ```swift
362
- // Only samples from the current app
363
- let sourcePredicate = HKQuery.predicateForObjects(
364
- from: HKSource.default()
365
- )
366
-
367
- // Only samples from Apple Watch
368
- let devicePredicate = HKQuery.predicateForObjects(
369
- withDeviceProperty: HKDevicePropertyKeyModel,
370
- allowedValues: ["Watch"]
371
- )
372
- ```
307
+ let today = HKQuery.predicateForSamples(withStart: todayStart, end: now)
308
+ let lastWeek = HKQuery.predicateForSamples(withStart: weekAgo, end: now)
309
+ let startsInWeek = HKQuery.predicateForSamples(withStart: weekAgo, end: now, options: .strictStartDate)
310
+ let mine = HKQuery.predicateForObjects(from: HKSource.default())
311
+ let fromWatch = HKQuery.predicateForObjects(withDeviceProperty: HKDevicePropertyKeyModel, allowedValues: ["Watch"])
373
312
 
374
- ### Compound Predicates
313
+ _ = (today, lastWeek)
314
+ return NSCompoundPredicate(andPredicateWithSubpredicates: [startsInWeek, mine, fromWatch])
315
+ }
375
316
 
376
- ```swift
377
- let todayFromWatch = NSCompoundPredicate(
378
- andPredicateWithSubpredicates: [todayPredicate, devicePredicate]
379
- )
380
-
381
- let descriptor = HKSampleQueryDescriptor(
382
- predicates: [.quantitySample(type: stepType, predicate: todayFromWatch)],
383
- sortDescriptors: [SortDescriptor(\.endDate, order: .reverse)]
384
- )
317
+ func watchStepsThisWeek(_ filter: NSPredicate) -> HKSampleQueryDescriptor<HKQuantitySample> {
318
+ HKSampleQueryDescriptor(
319
+ predicates: [.quantitySample(type: HKQuantityType(.stepCount), predicate: filter)],
320
+ sortDescriptors: []
321
+ )
322
+ }
385
323
  ```
386
324
 
387
- ## Statistics Collection for Charts
325
+ - `predicateForSamples(withStart:end:)` filters by time range.
326
+ - Adding `options: .strictStartDate` keeps only samples whose start date falls
327
+ inside the range.
328
+ - `predicateForObjects(from: HKSource.default())` keeps this app's own samples.
329
+ - `predicateForObjects(withDeviceProperty:allowedValues:)` filters by device,
330
+ here the model `"Watch"`.
331
+ - `NSCompoundPredicate(andPredicateWithSubpredicates:)` combines them before
332
+ they go into `.quantitySample(type:predicate:)`.
388
333
 
389
- ### Weekly Step Count Chart Data
334
+ ## Chart data
390
335
 
391
336
  ```swift
392
- struct DailyStepData: Identifiable {
393
- let id = UUID()
394
- let date: Date
395
- let steps: Double
337
+ import HealthKit
338
+
339
+ struct DayTotal: Identifiable {
340
+ let day: Date
341
+ let value: Double
342
+ var id: Date { day }
396
343
  }
397
344
 
398
- func fetchWeeklyStepData() async throws -> [DailyStepData] {
345
+ func lastSevenDaysOfSteps(_ store: HKHealthStore) async throws -> [DayTotal] {
399
346
  let calendar = Calendar.current
400
- let today = calendar.startOfDay(for: Date())
401
- let endDate = calendar.date(byAdding: .day, value: 1, to: today)!
402
- let startDate = calendar.date(byAdding: .day, value: -7, to: endDate)!
403
-
404
- let stepType = HKQuantityType(.stepCount)
405
- let predicate = HKQuery.predicateForSamples(
406
- withStart: startDate, end: endDate
407
- )
408
- let samplePredicate = HKSamplePredicate.quantitySample(
409
- type: stepType, predicate: predicate
410
- )
411
-
347
+ guard let end = calendar.date(byAdding: .day, value: 1, to: calendar.startOfDay(for: .now)),
348
+ let start = calendar.date(byAdding: .day, value: -7, to: end) else { return [] }
412
349
  let query = HKStatisticsCollectionQueryDescriptor(
413
- predicate: samplePredicate,
350
+ predicate: .quantitySample(type: HKQuantityType(.stepCount),
351
+ predicate: HKQuery.predicateForSamples(withStart: start, end: end)),
414
352
  options: .cumulativeSum,
415
- anchorDate: endDate,
353
+ anchorDate: end,
416
354
  intervalComponents: DateComponents(day: 1)
417
355
  )
418
-
419
- let collection = try await query.result(for: healthStore)
420
-
421
- var data: [DailyStepData] = []
422
- collection.statisticsCollection.enumerateStatistics(
423
- from: startDate, to: endDate
424
- ) { statistics, _ in
425
- let steps = statistics.sumQuantity()?
426
- .doubleValue(for: .count()) ?? 0
427
- data.append(DailyStepData(date: statistics.startDate, steps: steps))
356
+ var totals: [DayTotal] = []
357
+ try await query.result(for: store).enumerateStatistics(from: start, to: end) { stats, _ in
358
+ totals.append(DayTotal(day: stats.startDate, value: stats.sumQuantity()?.doubleValue(for: .count()) ?? 0))
428
359
  }
429
-
430
- return data
360
+ return totals
431
361
  }
432
- ```
433
-
434
- ### Hourly Heart Rate Averages
435
-
436
- ```swift
437
- func fetchHourlyHeartRate(for date: Date) async throws -> [(hour: Date, bpm: Double)] {
438
- let calendar = Calendar.current
439
- let startOfDay = calendar.startOfDay(for: date)
440
- let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)!
441
-
442
- let heartRateType = HKQuantityType(.heartRate)
443
- let predicate = HKQuery.predicateForSamples(
444
- withStart: startOfDay, end: endOfDay
445
- )
446
- let samplePredicate = HKSamplePredicate.quantitySample(
447
- type: heartRateType, predicate: predicate
448
- )
449
362
 
363
+ func hourlyPulse(_ store: HKHealthStore, on day: Date) async throws -> [DayTotal] {
364
+ let start = Calendar.current.startOfDay(for: day)
365
+ let end = start.addingTimeInterval(24 * 3_600)
450
366
  let query = HKStatisticsCollectionQueryDescriptor(
451
- predicate: samplePredicate,
367
+ predicate: .quantitySample(type: HKQuantityType(.heartRate),
368
+ predicate: HKQuery.predicateForSamples(withStart: start, end: end)),
452
369
  options: .discreteAverage,
453
- anchorDate: endOfDay,
370
+ anchorDate: start,
454
371
  intervalComponents: DateComponents(hour: 1)
455
372
  )
456
-
457
- let collection = try await query.result(for: healthStore)
458
- let unit = HKUnit.count().unitDivided(by: .minute())
459
-
460
- var hourlyData: [(hour: Date, bpm: Double)] = []
461
- collection.statisticsCollection.enumerateStatistics(
462
- from: startOfDay, to: endOfDay
463
- ) { statistics, _ in
464
- if let avg = statistics.averageQuantity()?.doubleValue(for: unit) {
465
- hourlyData.append((hour: statistics.startDate, bpm: avg))
373
+ let bpm = HKUnit.count().unitDivided(by: .minute())
374
+ var points: [DayTotal] = []
375
+ try await query.result(for: store).enumerateStatistics(from: start, to: end) { stats, _ in
376
+ if let average = stats.averageQuantity()?.doubleValue(for: bpm) {
377
+ points.append(DayTotal(day: stats.startDate, value: average))
466
378
  }
467
379
  }
468
-
469
- return hourlyData
380
+ return points
470
381
  }
471
382
  ```
472
383
 
473
- ## HealthKit + SwiftUI Integration
384
+ The weekly chart covers the seven days ending at the start of tomorrow and
385
+ anchors on that end date. The hourly chart uses `.discreteAverage` and adds a
386
+ point only for hours that have data.
474
387
 
475
- ### HealthKit Manager with `@Observable`
388
+ ## SwiftUI integration
476
389
 
477
390
  ```swift
478
391
  import HealthKit
392
+ import Observation
479
393
  import SwiftUI
480
394
 
481
395
  @Observable
482
396
  @MainActor
483
- final class HealthManager {
484
- let healthStore = HKHealthStore()
485
-
486
- var isAuthorized = false
487
- var todaySteps: Double = 0
488
- var recentHeartRate: Double = 0
489
-
490
- var isAvailable: Bool {
491
- HKHealthStore.isHealthDataAvailable()
492
- }
493
-
494
- func requestAuthorization() async throws {
495
- let typesToRead: Set<HKObjectType> = [
496
- HKQuantityType(.stepCount),
497
- HKQuantityType(.heartRate)
498
- ]
499
-
500
- try await healthStore.requestAuthorization(
501
- toShare: [],
502
- read: typesToRead
503
- )
504
- isAuthorized = true
397
+ final class VitalsModel {
398
+ private let store = HKHealthStore()
399
+ var isAvailable: Bool { HKHealthStore.isHealthDataAvailable() }
400
+ private(set) var isAuthorized = false
401
+ private(set) var steps: Double = 0
402
+ private(set) var restingPulse: Double?
403
+
404
+ func authorize() async {
405
+ do {
406
+ try await store.requestAuthorization(toShare: [], read: [
407
+ HKQuantityType(.stepCount), HKQuantityType(.restingHeartRate)
408
+ ])
409
+ isAuthorized = true
410
+ } catch {
411
+ isAuthorized = false
412
+ }
505
413
  }
506
414
 
507
- func refreshData() async {
508
- async let steps = fetchTodaySteps()
509
- async let heartRate = fetchLatestHeartRate()
510
-
511
- todaySteps = (try? await steps) ?? 0
512
- recentHeartRate = (try? await heartRate) ?? 0
415
+ func refresh() async {
416
+ async let stepTotal = todaysSteps()
417
+ async let pulse = latestRestingPulse()
418
+ steps = (try? await stepTotal) ?? 0
419
+ restingPulse = try? await pulse
513
420
  }
514
421
 
515
- private func fetchTodaySteps() async throws -> Double {
516
- let calendar = Calendar.current
517
- let startOfDay = calendar.startOfDay(for: Date())
518
- let predicate = HKQuery.predicateForSamples(
519
- withStart: startOfDay, end: Date()
520
- )
521
- let stepType = HKQuantityType(.stepCount)
522
- let samplePredicate = HKSamplePredicate.quantitySample(
523
- type: stepType, predicate: predicate
524
- )
422
+ private func todaysSteps() async throws -> Double {
423
+ let start = Calendar.current.startOfDay(for: .now)
525
424
  let query = HKStatisticsQueryDescriptor(
526
- predicate: samplePredicate, options: .cumulativeSum
425
+ predicate: .quantitySample(type: HKQuantityType(.stepCount),
426
+ predicate: HKQuery.predicateForSamples(withStart: start, end: .now)),
427
+ options: .cumulativeSum
527
428
  )
528
- return try await query.result(for: healthStore)?
529
- .sumQuantity()?.doubleValue(for: .count()) ?? 0
429
+ return try await query.result(for: store)?.sumQuantity()?.doubleValue(for: .count()) ?? 0
530
430
  }
531
431
 
532
- private func fetchLatestHeartRate() async throws -> Double {
533
- let heartRateType = HKQuantityType(.heartRate)
534
- let descriptor = HKSampleQueryDescriptor(
535
- predicates: [.quantitySample(type: heartRateType)],
432
+ private func latestRestingPulse() async throws -> Double? {
433
+ let query = HKSampleQueryDescriptor(
434
+ predicates: [.quantitySample(type: HKQuantityType(.restingHeartRate))],
536
435
  sortDescriptors: [SortDescriptor(\.endDate, order: .reverse)],
537
436
  limit: 1
538
437
  )
539
- let results = try await descriptor.result(for: healthStore)
540
- let unit = HKUnit.count().unitDivided(by: .minute())
541
- return results.first?.quantity.doubleValue(for: unit) ?? 0
438
+ return try await query.result(for: store).first?.quantity
439
+ .doubleValue(for: .count().unitDivided(by: .minute()))
542
440
  }
543
441
  }
544
- ```
545
442
 
546
- ### SwiftUI View with HealthKit
547
-
548
- ```swift
549
- struct HealthDashboardView: View {
550
- @Environment(HealthManager.self) private var healthManager
443
+ struct VitalsScreen: View {
444
+ @Environment(VitalsModel.self) private var model
551
445
 
552
446
  var body: some View {
553
- NavigationStack {
554
- Group {
555
- if !healthManager.isAvailable {
556
- ContentUnavailableView(
557
- "HealthKit Unavailable",
558
- systemImage: "heart.slash",
559
- description: Text("This device does not support HealthKit.")
560
- )
561
- } else if !healthManager.isAuthorized {
562
- authorizationPrompt
563
- } else {
564
- healthDataView
565
- }
566
- }
567
- .navigationTitle("Health")
568
- }
569
- }
570
-
571
- private var authorizationPrompt: some View {
572
- ContentUnavailableView {
573
- Label("Health Access", systemImage: "heart.text.square")
574
- } description: {
575
- Text("Grant access to view your health data.")
576
- } actions: {
577
- Button("Authorize") {
578
- Task {
579
- try? await healthManager.requestAuthorization()
580
- }
447
+ if !model.isAvailable {
448
+ ContentUnavailableView("Health data unavailable", systemImage: "heart.slash")
449
+ } else if !model.isAuthorized {
450
+ ContentUnavailableView {
451
+ Label("Connect to Health", systemImage: "heart.text.square")
452
+ } actions: {
453
+ Button("Allow access") { Task { await model.authorize() } }
581
454
  }
582
- .buttonStyle(.borderedProminent)
583
- }
584
- }
585
-
586
- private var healthDataView: some View {
587
- List {
588
- Section("Today") {
589
- LabeledContent("Steps") {
590
- Text(healthManager.todaySteps, format: .number.precision(.fractionLength(0)))
591
- }
592
- LabeledContent("Heart Rate") {
593
- Text("\(Int(healthManager.recentHeartRate)) bpm")
594
- }
455
+ } else {
456
+ List {
457
+ LabeledContent("Steps today", value: model.steps, format: .number)
458
+ LabeledContent("Resting pulse", value: model.restingPulse.map { "\(Int($0)) bpm" } ?? "-")
595
459
  }
596
- }
597
- .task {
598
- await healthManager.refreshData()
599
- }
600
- .refreshable {
601
- await healthManager.refreshData()
460
+ .task { await model.refresh() }
461
+ .refreshable { await model.refresh() }
602
462
  }
603
463
  }
604
464
  }
605
- ```
606
465
 
607
- ### App Entry Point Wiring
608
-
609
- ```swift
610
- @main
611
- struct MyHealthApp: App {
612
- @State private var healthManager = HealthManager()
466
+ struct VitalsApp: App {
467
+ @State private var model = VitalsModel()
613
468
 
614
469
  var body: some Scene {
615
470
  WindowGroup {
616
- HealthDashboardView()
617
- .environment(healthManager)
471
+ VitalsScreen().environment(model)
618
472
  }
619
473
  }
620
474
  }
621
475
  ```
622
476
 
623
- ## Characteristic Types
477
+ - The model requests read-only access by passing an empty `toShare` set.
478
+ - `refresh()` runs both fetches at once with `async let`.
479
+ - The latest value comes from a sample descriptor with `limit: 1` sorted by end
480
+ date, newest first.
481
+ - The screen checks availability first, then authorization, then shows the
482
+ data. `isAuthorized` becomes `true` once `requestAuthorization` returns
483
+ without throwing, which only means the sheet was handled, not that reads were
484
+ allowed.
485
+ - The app creates the model as `@State` and injects it with `.environment`.
624
486
 
625
- Characteristic types are read-only values set by the user in the Health app. They do not require sample queries.
487
+ ## Characteristics
488
+
489
+ Characteristics are values the user enters once in Health, such as blood type.
490
+ They are read directly; no query is involved, and all of these calls throw.
626
491
 
627
492
  ```swift
628
- func readCharacteristics() throws {
629
- // Date of birth
630
- let dobComponents = try healthStore.dateOfBirthComponents()
631
- let calendar = Calendar.current
632
- if let dob = calendar.date(from: dobComponents) {
633
- let age = calendar.dateComponents([.year], from: dob, to: Date()).year ?? 0
634
- print("Age: \(age)")
635
- }
493
+ import HealthKit
636
494
 
637
- // Biological sex
638
- let biologicalSex = try healthStore.biologicalSex().biologicalSex
639
- switch biologicalSex {
640
- case .female: print("Female")
641
- case .male: print("Male")
642
- case .other: print("Other")
643
- case .notSet: print("Not set")
644
- @unknown default: break
645
- }
495
+ struct Profile {
496
+ var age: Int?
497
+ var sex: HKBiologicalSex = .notSet
498
+ var blood: HKBloodType = .notSet
499
+ var skin: HKFitzpatrickSkinType = .notSet
500
+ var wheelchair: HKWheelchairUse = .notSet
501
+ }
646
502
 
647
- // Blood type
648
- let bloodType = try healthStore.bloodType().bloodType
649
- switch bloodType {
650
- case .aPositive: print("A+")
651
- case .aNegative: print("A-")
652
- case .bPositive: print("B+")
653
- case .bNegative: print("B-")
654
- case .abPositive: print("AB+")
655
- case .abNegative: print("AB-")
656
- case .oPositive: print("O+")
657
- case .oNegative: print("O-")
658
- case .notSet: print("Not set")
659
- @unknown default: break
503
+ func loadProfile(_ store: HKHealthStore) -> Profile {
504
+ var profile = Profile()
505
+ do {
506
+ let born = try store.dateOfBirthComponents()
507
+ if let birthday = Calendar.current.date(from: born) {
508
+ profile.age = Calendar.current.dateComponents([.year], from: birthday, to: .now).year
509
+ }
510
+ } catch let error as HKError where error.code == .errorNoData {
511
+ profile.age = nil
512
+ } catch {
513
+ profile.age = nil
660
514
  }
661
-
662
- // Fitzpatrick skin type
663
- let skinType = try healthStore.fitzpatrickSkinType().skinType
664
- print("Skin type: \(skinType.rawValue)")
665
-
666
- // Wheelchair use
667
- let wheelchair = try healthStore.wheelchairUse().wheelchairUse
668
- print("Wheelchair: \(wheelchair == .yes)")
515
+ profile.sex = (try? store.biologicalSex().biologicalSex) ?? .notSet
516
+ profile.blood = (try? store.bloodType().bloodType) ?? .notSet
517
+ profile.skin = (try? store.fitzpatrickSkinType().skinType) ?? .notSet
518
+ profile.wheelchair = (try? store.wheelchairUse().wheelchairUse) ?? .notSet
519
+ return profile
669
520
  }
670
521
  ```
671
522
 
672
- **Important:** These throw an error if the value is not set, so always use `try` and handle the `HKError.errorNoData` case.
523
+ | Type | Cases |
524
+ |---|---|
525
+ | `HKBiologicalSex` | `.female`, `.male`, `.other`, `.notSet` |
526
+ | `HKBloodType` | `.aPositive`, `.aNegative`, `.bPositive`, `.bNegative`, `.abPositive`, `.abNegative`, `.oPositive`, `.oNegative`, `.notSet` |
673
527
 
674
- ```swift
675
- do {
676
- let dob = try healthStore.dateOfBirthComponents()
677
- // Use dob
678
- } catch let error as HKError where error.code == .errorNoData {
679
- // User hasn't set date of birth in Health app
680
- } catch {
681
- // Other error
682
- }
683
- ```
528
+ An unset value throws `HKError` with code `.errorNoData`; catch that case
529
+ separately from real failures.