@mmerterden/multi-agent-pipeline 20.6.0 → 20.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +328 -329
  5. package/package.json +2 -2
  6. package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
  7. package/pipeline/scripts/_notices.mjs +12 -1
  8. package/pipeline/scripts/gen-skills-index.mjs +1 -1
  9. package/pipeline/skills/.skill-manifest.json +106 -106
  10. package/pipeline/skills/shared/README.md +71 -71
  11. package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
  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/android-architecture/SKILL.md +2 -0
  16. package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
  17. package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
  18. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  19. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  20. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  21. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  22. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  23. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  24. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  25. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  26. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  27. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  28. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  29. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  30. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  31. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  32. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  33. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  34. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  35. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  36. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  37. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  38. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  39. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  40. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  41. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  42. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  43. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  44. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  45. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  46. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  47. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  48. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  49. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  50. package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
  51. package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
  52. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  53. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  54. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  55. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +1 -0
  56. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  57. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  58. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  59. package/pipeline/skills/shared/external/compose-components/SKILL.md +2 -0
  60. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
  61. package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
  62. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  63. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  64. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  65. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  66. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  67. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  68. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  69. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  70. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  71. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  72. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  73. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  74. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  75. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  76. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  77. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  78. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  79. package/pipeline/skills/shared/external/council/SKILL.md +1 -0
  80. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  81. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  82. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  83. package/pipeline/skills/shared/external/css-modern/SKILL.md +1 -0
  84. package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
  85. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  86. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  87. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  88. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  89. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  90. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  91. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  92. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  93. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  94. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  95. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  96. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  97. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  98. package/pipeline/skills/shared/external/evidence-github/SKILL.md +2 -0
  99. package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
  100. package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
  101. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  102. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  103. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  104. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  105. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  106. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  107. package/pipeline/skills/shared/external/html-semantic/SKILL.md +1 -0
  108. package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
  109. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  110. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  111. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  112. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  113. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  114. package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +1 -0
  115. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  116. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  117. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  118. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  119. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +1 -0
  120. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  121. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  122. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  123. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  124. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  125. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  126. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  127. package/pipeline/skills/shared/external/ios-security/SKILL.md +2 -0
  128. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  129. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  130. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  131. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  132. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  133. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  134. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +91 -283
  135. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
  136. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
  137. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
  138. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
  139. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
  140. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
  141. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
  142. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
  143. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
  144. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
  145. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
  146. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
  147. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
  148. package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
  149. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
  150. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  151. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  152. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  153. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  154. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  155. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  156. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  157. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  158. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  159. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  160. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  161. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  162. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  163. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +1 -0
  164. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  165. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  166. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  167. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  168. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  169. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  170. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  171. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  172. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  173. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  174. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  175. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  176. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  177. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  178. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  179. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  180. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  181. package/pipeline/skills/shared/external/play-store-review/SKILL.md +2 -0
  182. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  183. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  184. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  185. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  186. package/pipeline/skills/shared/external/python-patterns/SKILL.md +1 -0
  187. package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
  188. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  189. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  190. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  191. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +1 -0
  192. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
  193. package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
  194. package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
  195. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  196. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  197. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  198. package/pipeline/skills/shared/external/signal-community/SKILL.md +2 -0
  199. package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
  200. package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
  201. package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
  202. package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
  203. package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
  204. package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
  205. package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
  206. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  207. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  208. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  209. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  210. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  211. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  212. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  213. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  214. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  215. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  216. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  217. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  218. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  219. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  220. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  221. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  222. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  223. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  224. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  225. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  226. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  227. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  228. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  229. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  230. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  231. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  232. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  233. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  234. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  235. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  236. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  237. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  238. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  239. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  240. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  241. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  242. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  243. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  244. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  245. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  246. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  247. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  248. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  249. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  250. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  251. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  252. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  253. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  254. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  255. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  256. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  257. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  258. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  259. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  260. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  261. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  262. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  263. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  264. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  265. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  266. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  267. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  268. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  269. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  270. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  271. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  272. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  273. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  274. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  275. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  276. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  277. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  278. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  279. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  280. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  281. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  282. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  283. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  284. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  285. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  286. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  287. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  288. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  289. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  290. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  291. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  292. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  293. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  294. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  295. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  296. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  297. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  298. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  299. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  300. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  301. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  302. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  303. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  304. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  305. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  306. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  307. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  308. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  309. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  310. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +1 -0
  311. package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
  312. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  313. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  314. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +1 -0
  315. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  316. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  317. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  318. package/pipeline/skills/shared/external/vue-composition/SKILL.md +1 -0
  319. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  320. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  321. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -0
  322. package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
  323. package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
  324. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  325. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  326. 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.