@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +285 -285
  5. package/package.json +3 -3
  6. package/pipeline/lib/redact.mjs +3 -2
  7. package/pipeline/scripts/_notices.mjs +1 -1
  8. package/pipeline/scripts/gen-skills-index.mjs +13 -1
  9. package/pipeline/scripts/pre-commit-check.sh +4 -0
  10. package/pipeline/skills/.skill-manifest.json +69 -69
  11. package/pipeline/skills/shared/README.md +70 -70
  12. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  13. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  14. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  15. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  16. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  17. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  18. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  19. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  20. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  21. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  22. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  23. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +345 -277
  24. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  25. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +107 -121
  26. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
  27. package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -326
  28. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  29. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  30. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  31. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +335 -360
  32. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  34. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  35. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  36. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  37. package/pipeline/skills/shared/external/authentication/SKILL.md +277 -381
  38. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  39. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -178
  40. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  41. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  42. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  43. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  44. package/pipeline/skills/shared/external/background-processing/SKILL.md +274 -384
  45. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  46. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
  47. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  48. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  49. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  50. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  51. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  52. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  53. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  54. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  55. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  56. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +228 -376
  57. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  58. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  59. package/pipeline/skills/shared/external/core-data/SKILL.md +302 -368
  60. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  61. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  62. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  63. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  64. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  65. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  66. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  67. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  68. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  69. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  70. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  71. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  72. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  73. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  74. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  75. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  76. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  77. package/pipeline/skills/shared/external/device-integrity/SKILL.md +236 -353
  78. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  79. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  80. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  81. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  82. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  83. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  84. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  85. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  86. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  87. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  88. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  89. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  90. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  91. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  92. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  93. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  94. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  95. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  96. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  97. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  98. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  99. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  100. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  101. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  102. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  103. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  104. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  105. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  106. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  107. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  108. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  109. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  111. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  112. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  113. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  114. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +3 -3
  115. package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
  116. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
  117. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
  118. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
  119. package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
  120. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
  121. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
  122. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
  123. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
  124. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
  125. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
  126. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
  127. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
  128. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
  129. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
  130. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -267
  131. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  132. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  133. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  134. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  135. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  136. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  137. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  138. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  139. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  140. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  141. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  142. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  143. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  144. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  145. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  146. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  147. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  148. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  149. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  150. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  151. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  152. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  153. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  154. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  155. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  156. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  157. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  158. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  159. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  160. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  161. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  162. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  163. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  164. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  165. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  166. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  167. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  168. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  169. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  170. package/pipeline/skills/shared/external/skill-creator/template.md +7 -1
  171. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  172. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  173. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  174. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  175. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  176. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  177. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +302 -241
  178. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  179. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  180. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  181. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  182. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  183. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  184. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  185. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  186. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  187. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  188. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  189. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  190. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  191. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  192. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +304 -351
  193. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  194. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  195. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  196. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  197. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  198. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  199. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  200. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  201. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  202. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  203. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  204. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  205. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  206. package/pipeline/skills/shared/external/swift-security/SKILL.md +183 -162
  207. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  208. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  209. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +411 -476
  210. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  211. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  212. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  213. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  214. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  215. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  216. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +375 -491
  217. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  218. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  219. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  220. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +397 -457
  221. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  222. package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
  223. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  224. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
  225. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  226. package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -256
  227. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  228. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  229. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  230. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  231. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  232. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  233. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  234. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  235. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  236. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  237. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  238. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  239. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  240. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  241. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  242. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  243. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  244. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  245. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  246. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  247. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  248. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  249. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  250. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  251. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +201 -168
  252. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  253. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
  254. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  255. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
  256. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  257. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  258. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  259. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  260. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  261. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  262. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  263. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  264. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  265. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  266. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  267. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  268. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  269. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  270. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  271. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  272. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  273. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  274. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  275. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  276. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  277. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  278. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  279. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  280. package/pipeline/skills/shared/external/weatherkit/SKILL.md +160 -315
  281. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  282. package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
  283. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -719
  284. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,498 +1,350 @@
1
1
  ---
2
2
  name: core-bluetooth
3
- description: "Build direct Bluetooth Low Energy workflows with Core Bluetooth. Use when implementing BLE central or peripheral GATT communication, scanning or connecting with CBCentralManager, discovering services and characteristics, reading/writing/subscribing with CBPeripheral, publishing local services with CBPeripheralManager, handling Bluetooth authorization, background BLE modes, state restoration, write flow control, or CBUUID-based workflows. For privacy-preserving accessory setup/picker flows, use accessorysetupkit first and return here for post-setup GATT communication."
3
+ description: "Direct BLE with Core Bluetooth: central and peripheral GATT communication, CBCentralManager scanning and connecting, service and characteristic discovery, CBPeripheral reads, writes and subscriptions, CBPeripheralManager local services, Bluetooth authorization, background BLE modes, state restoration, write flow control, CBUUID workflows. Use when an app talks to BLE accessories or acts as one. Not for privacy-preserving accessory setup or picker flows: AccessorySetupKit first, then GATT here."
4
4
  metadata:
5
- source: "dpearson2699/swift-ios-skills (PolyForm Perimeter 1.0.0)"
5
+ source: multi-agent-pipeline
6
6
  ---
7
7
 
8
8
  # Core Bluetooth
9
9
 
10
- Scan for, connect to, and exchange data with Bluetooth Low Energy (BLE) devices.
11
- Covers the central role (scanning and connecting to peripherals), the peripheral
12
- role (advertising services), background modes, and state restoration.
13
- Targets Swift 6.3 / iOS 26+.
14
- Use `accessorysetupkit` for privacy-preserving accessory discovery and setup;
15
- use this skill for direct Core Bluetooth GATT communication.
16
-
17
- ## Contents
18
-
19
- - [Setup](#setup)
20
- - [Central Role: Scanning](#central-role-scanning)
21
- - [Central Role: Connecting](#central-role-connecting)
22
- - [Discovering Services and Characteristics](#discovering-services-and-characteristics)
23
- - [Reading, Writing, and Notifications](#reading-writing-and-notifications)
24
- - [Peripheral Role: Advertising](#peripheral-role-advertising)
25
- - [Background BLE](#background-ble)
26
- - [State Restoration](#state-restoration)
27
- - [Common Mistakes](#common-mistakes)
28
- - [Review Checklist](#review-checklist)
29
- - [References](#references)
10
+ Core Bluetooth lets an app act as a BLE **central** (scan, connect, read and
11
+ write another device) or as a **peripheral** (publish services others connect
12
+ to). This skill covers both roles, background modes and state restoration, for
13
+ Swift 6.3 and iOS 26.
14
+
15
+ Finding and pairing an accessory through the system picker, without broad
16
+ Bluetooth permission, is AccessorySetupKit's job. Once the user has picked the
17
+ accessory, the GATT conversation (services, characteristics, reads, writes,
18
+ notifications) happens here.
30
19
 
31
20
  ## Setup
32
21
 
33
- ### Info.plist Keys
22
+ ### Info.plist
34
23
 
35
24
  | Key | Purpose |
36
- |---|---|
37
- | `NSBluetoothAlwaysUsageDescription` | Required. Explains why the app uses Bluetooth |
38
- | `UIBackgroundModes` with `bluetooth-central` | Background scanning and connecting |
39
- | `UIBackgroundModes` with `bluetooth-peripheral` | Background advertising |
25
+ |-----|---------|
26
+ | `NSBluetoothAlwaysUsageDescription` | Required. Tells the user why the app uses Bluetooth |
27
+ | `UIBackgroundModes` with `bluetooth-central` | Scan and stay connected in the background |
28
+ | `UIBackgroundModes` with `bluetooth-peripheral` | Advertise and serve in the background |
40
29
 
41
- ### Bluetooth Authorization
30
+ ### Permission and power state
42
31
 
43
- Core Bluetooth has no explicit permission request API. Add
44
- `NSBluetoothAlwaysUsageDescription`, create the manager when the app is ready for
45
- Bluetooth access, then check `manager.authorization` and `manager.state`.
46
- Treat `.denied` and `.restricted` as terminal until the user changes Settings;
47
- wait for `.poweredOn` before scanning, connecting, advertising, or publishing
48
- services.
32
+ There is no "request Bluetooth permission" call. The system asks the first
33
+ time the app creates a manager, so create one only when the user reaches the
34
+ feature that needs it. Then look at two things:
49
35
 
50
- ## Central Role: Scanning
36
+ - `CBManager.authorization` (a class property from iOS 13.1; the instance
37
+ property was deprecated after 13.0): `.denied` and `.restricted` stay that way
38
+ until the user changes Settings, so show guidance rather than retrying. Being a
39
+ class property, it can be read before any manager is created.
40
+ - `manager.state`: do nothing (scan, connect, advertise, add services) until it
41
+ is `.poweredOn`.
51
42
 
52
- ### Creating the Central Manager
43
+ ## Central role
53
44
 
54
- Always wait for the `poweredOn` state before scanning.
45
+ ### Scanning
55
46
 
56
47
  ```swift
57
48
  import CoreBluetooth
58
49
 
59
- final class BluetoothManager: NSObject, CBCentralManagerDelegate {
60
- private var centralManager: CBCentralManager!
61
- private var discoveredPeripheral: CBPeripheral?
50
+ final class ScaleLink: NSObject, CBCentralManagerDelegate {
51
+ static var weightService: CBUUID { CBUUID(string: "181D") }
52
+
53
+ private var central: CBCentralManager!
54
+ private var scale: CBPeripheral?
62
55
 
63
56
  override init() {
64
57
  super.init()
65
- centralManager = CBCentralManager(delegate: self, queue: nil)
58
+ central = CBCentralManager(delegate: self, queue: nil)
66
59
  }
67
60
 
68
61
  func centralManagerDidUpdateState(_ central: CBCentralManager) {
69
62
  guard central.state == .poweredOn else { return }
70
- startScanning()
63
+ central.scanForPeripherals(
64
+ withServices: [Self.weightService],
65
+ options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
66
+ )
67
+ }
68
+
69
+ func centralManager(_ central: CBCentralManager, didDiscover found: CBPeripheral,
70
+ advertisementData: [String: Any], rssi: NSNumber) {
71
+ guard rssi.intValue > -75 else { return }
72
+ scale = found
73
+ central.stopScan()
74
+ central.connect(found)
71
75
  }
72
76
  }
73
77
  ```
74
78
 
75
- ### Scanning for Peripherals
79
+ - Scan for the service UUIDs you care about. `withServices: nil` finds
80
+ everything, burns battery and has no place in a shipping app.
81
+ - Ignoring weak signals (here anything at or below -75 dBm) is optional.
82
+ - Keep a strong reference to the discovered `CBPeripheral`. Core Bluetooth
83
+ does not, and a released peripheral silently never connects.
84
+ - Stop scanning as soon as the target turns up, then connect.
76
85
 
77
- Scan for specific service UUIDs to save power. Pass `nil` to discover all
78
- peripherals (not recommended in production).
86
+ The implicitly unwrapped `central` exists only because `self` must be fully
87
+ initialized before it can be the delegate. `CBUUID` is not `Sendable`, and
88
+ Swift 6 rejects a stored `static let` of it on a type without actor isolation,
89
+ so the UUIDs are computed static properties. A `@MainActor` type can keep
90
+ stored ones, as the SwiftUI monitor in the reference does.
79
91
 
80
- ```swift
81
- let heartRateServiceUUID = CBUUID(string: "180D")
82
-
83
- func startScanning() {
84
- centralManager.scanForPeripherals(
85
- withServices: [heartRateServiceUUID],
86
- options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
87
- )
88
- }
89
-
90
- func centralManager(
91
- _ central: CBCentralManager,
92
- didDiscover peripheral: CBPeripheral,
93
- advertisementData: [String: Any],
94
- rssi RSSI: NSNumber
95
- ) {
96
- guard RSSI.intValue > -70 else { return } // Filter weak signals
97
-
98
- // IMPORTANT: Retain the peripheral -- it will be deallocated otherwise
99
- discoveredPeripheral = peripheral
100
- centralManager.stopScan()
101
- centralManager.connect(peripheral, options: nil)
102
- }
103
- ```
104
-
105
- ## Central Role: Connecting
92
+ ### Connecting and disconnecting
106
93
 
107
94
  ```swift
108
- func centralManager(
109
- _ central: CBCentralManager,
110
- didConnect peripheral: CBPeripheral
111
- ) {
112
- peripheral.delegate = self
113
- peripheral.discoverServices([heartRateServiceUUID])
114
- }
95
+ extension ScaleLink {
96
+ func centralManager(_ central: CBCentralManager, didConnect device: CBPeripheral) {
97
+ device.delegate = self
98
+ device.discoverServices([Self.weightService])
99
+ }
115
100
 
116
- func centralManager(
117
- _ central: CBCentralManager,
118
- didDisconnectPeripheral peripheral: CBPeripheral,
119
- timestamp: CFAbsoluteTime,
120
- isReconnecting: Bool,
121
- error: Error?
122
- ) {
123
- if isReconnecting {
124
- // System is automatically reconnecting
125
- return
101
+ func centralManager(_ central: CBCentralManager, didDisconnectPeripheral device: CBPeripheral,
102
+ timestamp: CFAbsoluteTime, isReconnecting: Bool, error: (any Error)?) {
103
+ if isReconnecting { return }
104
+ scale = nil
126
105
  }
127
- // Handle disconnection -- optionally reconnect
128
- discoveredPeripheral = nil
129
106
  }
130
107
  ```
131
108
 
132
- ## Discovering Services and Characteristics
109
+ Set the peripheral's delegate before asking it for anything. When
110
+ `isReconnecting` is true the system is already reconnecting on your behalf;
111
+ otherwise decide whether to reconnect yourself and release the reference.
133
112
 
134
- Implement `CBPeripheralDelegate` to walk the service/characteristic tree.
113
+ ## Discovering services and characteristics
135
114
 
136
115
  ```swift
137
- extension BluetoothManager: CBPeripheralDelegate {
138
- func peripheral(
139
- _ peripheral: CBPeripheral,
140
- didDiscoverServices error: Error?
141
- ) {
142
- guard let services = peripheral.services else { return }
143
- for service in services {
144
- peripheral.discoverCharacteristics(nil, for: service)
116
+ extension ScaleLink: CBPeripheralDelegate {
117
+ static var weightMeasurement: CBUUID { CBUUID(string: "2A9D") }
118
+
119
+ func peripheral(_ device: CBPeripheral, didDiscoverServices error: (any Error)?) {
120
+ for service in device.services ?? [] {
121
+ device.discoverCharacteristics([Self.weightMeasurement, CBUUID(string: "2A19")], for: service)
145
122
  }
146
123
  }
147
124
 
148
- func peripheral(
149
- _ peripheral: CBPeripheral,
150
- didDiscoverCharacteristicsFor service: CBService,
151
- error: Error?
152
- ) {
153
- guard let characteristics = service.characteristics else { return }
154
- for characteristic in characteristics {
155
- if characteristic.properties.contains(.notify) {
156
- peripheral.setNotifyValue(true, for: characteristic)
125
+ func peripheral(_ device: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: (any Error)?) {
126
+ for trait in service.characteristics ?? [] {
127
+ if trait.properties.contains(.notify) {
128
+ device.setNotifyValue(true, for: trait)
157
129
  }
158
- if characteristic.properties.contains(.read) {
159
- peripheral.readValue(for: characteristic)
130
+ if trait.properties.contains(.read) {
131
+ device.readValue(for: trait)
160
132
  }
161
133
  }
162
134
  }
163
135
  }
164
136
  ```
165
137
 
166
- ## Reading, Writing, and Notifications
138
+ Check `properties` before each read, write or subscription.
167
139
 
168
- ### Reading a Value
140
+ ## Reading, writing and notifications
141
+
142
+ Reads and notifications arrive in the same callback:
169
143
 
170
144
  ```swift
171
- func peripheral(
172
- _ peripheral: CBPeripheral,
173
- didUpdateValueFor characteristic: CBCharacteristic,
174
- error: Error?
175
- ) {
176
- guard let data = characteristic.value else { return }
177
-
178
- switch characteristic.uuid {
145
+ func peripheral(_ device: CBPeripheral, didUpdateValueFor trait: CBCharacteristic, error: (any Error)?) {
146
+ guard error == nil, let bytes = trait.value else { return }
147
+ switch trait.uuid {
179
148
  case CBUUID(string: "2A37"):
180
- if let heartRate = parseHeartRate(data) {
181
- print("Heart rate: \(heartRate) bpm")
182
- }
149
+ pulse = beatsPerMinute(bytes)
183
150
  case CBUUID(string: "2A19"):
184
- let batteryLevel = data.first.map { Int($0) } ?? 0
185
- print("Battery: \(batteryLevel)%")
151
+ batteryPercent = bytes.first.map(Int.init)
186
152
  default:
187
153
  break
188
154
  }
189
155
  }
156
+ ```
190
157
 
191
- private func parseHeartRate(_ data: Data) -> Int? {
192
- guard data.count >= 2 else { return nil }
193
- let flags = data[0]
194
- let is16Bit = (flags & 0x01) != 0
195
- if is16Bit {
196
- guard data.count >= 3 else { return nil }
197
- return Int(data[1]) | (Int(data[2]) << 8)
198
- } else {
199
- return Int(data[1])
158
+ Heart Rate Measurement (`2A37`) needs care with its flags byte:
159
+
160
+ ```swift
161
+ func beatsPerMinute(_ bytes: Data) -> Int? {
162
+ guard bytes.count >= 2 else { return nil }
163
+ let wide = bytes[bytes.startIndex] & 0x01 != 0
164
+ if wide {
165
+ guard bytes.count >= 3 else { return nil }
166
+ let low = UInt16(bytes[bytes.startIndex + 1])
167
+ let high = UInt16(bytes[bytes.startIndex + 2])
168
+ return Int(low | high << 8)
200
169
  }
170
+ return Int(bytes[bytes.startIndex + 1])
201
171
  }
202
172
  ```
203
173
 
204
- ### Writing a Value
174
+ Bit 0 set means a 16-bit little-endian value in bytes 1 and 2; otherwise the
175
+ value is the single byte 1.
176
+
177
+ ### Choosing a write type
205
178
 
206
179
  ```swift
207
- func writeValue(_ data: Data, to characteristic: CBCharacteristic,
208
- on peripheral: CBPeripheral,
209
- preferResponse: Bool = true) {
180
+ func send(_ payload: Data, to trait: CBCharacteristic, on device: CBPeripheral, confirm: Bool) -> Bool {
210
181
  let type: CBCharacteristicWriteType
211
- if preferResponse, characteristic.properties.contains(.write) {
182
+ if confirm, trait.properties.contains(.write) {
212
183
  type = .withResponse
213
- } else if characteristic.properties.contains(.writeWithoutResponse),
214
- peripheral.canSendWriteWithoutResponse {
184
+ } else if trait.properties.contains(.writeWithoutResponse), device.canSendWriteWithoutResponse {
215
185
  type = .withoutResponse
216
- } else if characteristic.properties.contains(.write) {
186
+ } else if trait.properties.contains(.write) {
217
187
  type = .withResponse
218
188
  } else {
219
- return
220
- }
221
-
222
- guard data.count <= peripheral.maximumWriteValueLength(for: type) else { return }
223
- peripheral.writeValue(data, for: characteristic, type: type)
224
- }
225
-
226
- // Confirmation callback for .withResponse writes.
227
- func peripheral(
228
- _ peripheral: CBPeripheral,
229
- didWriteValueFor characteristic: CBCharacteristic,
230
- error: Error?
231
- ) {
232
- if let error {
233
- print("Write failed: \(error.localizedDescription)")
189
+ return false
234
190
  }
191
+ guard payload.count <= device.maximumWriteValueLength(for: type) else { return false }
192
+ device.writeValue(payload, for: trait, type: type)
193
+ return true
235
194
  }
236
-
237
- // Resume queued .withoutResponse writes here.
238
- func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {}
239
195
  ```
240
196
 
241
- ### Subscribing to Notifications
197
+ - `.withResponse` writes are confirmed in `peripheral(_:didWriteValueFor:error:)`.
198
+ - When `canSendWriteWithoutResponse` is false, queue the data and resume in
199
+ `peripheralIsReady(toSendWriteWithoutResponse:)`.
200
+ - Payloads longer than `maximumWriteValueLength(for:)` must be split.
242
201
 
243
- ```swift
244
- // Subscribe
245
- peripheral.setNotifyValue(true, for: characteristic)
246
-
247
- // Unsubscribe
248
- peripheral.setNotifyValue(false, for: characteristic)
249
-
250
- // Confirmation
251
- func peripheral(
252
- _ peripheral: CBPeripheral,
253
- didUpdateNotificationStateFor characteristic: CBCharacteristic,
254
- error: Error?
255
- ) {
256
- if characteristic.isNotifying {
257
- print("Now receiving notifications for \(characteristic.uuid)")
258
- }
259
- }
260
- ```
202
+ ### Subscriptions
261
203
 
262
- ## Peripheral Role: Advertising
204
+ `setNotifyValue(true, for:)` subscribes and `setNotifyValue(false, for:)`
205
+ unsubscribes. The outcome arrives in
206
+ `peripheral(_:didUpdateNotificationStateFor:error:)`, where
207
+ `characteristic.isNotifying` tells you the current state.
263
208
 
264
- Publish services from the local device using `CBPeripheralManager`.
209
+ ## Peripheral role
265
210
 
266
211
  ```swift
267
- final class BLEPeripheralManager: NSObject, CBPeripheralManagerDelegate {
268
- private var peripheralManager: CBPeripheralManager!
269
- private let serviceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABC")
270
- private let charUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABD")
212
+ final class ThermometerBeacon: NSObject, CBPeripheralManagerDelegate {
213
+ static var service: CBUUID { CBUUID(string: "5E1A7D30-2C4B-4F55-9B0E-7A1C3D2E8F10") }
214
+ static var reading: CBUUID { CBUUID(string: "5E1A7D31-2C4B-4F55-9B0E-7A1C3D2E8F10") }
215
+
216
+ private var manager: CBPeripheralManager!
271
217
 
272
218
  override init() {
273
219
  super.init()
274
- peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
275
- }
276
-
277
- func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
278
- guard peripheral.state == .poweredOn else { return }
279
- setupService()
220
+ manager = CBPeripheralManager(delegate: self, queue: nil)
280
221
  }
281
222
 
282
- private func setupService() {
283
- let characteristic = CBMutableCharacteristic(
284
- type: charUUID,
223
+ func peripheralManagerDidUpdateState(_ manager: CBPeripheralManager) {
224
+ guard manager.state == .poweredOn else { return }
225
+ let reading = CBMutableCharacteristic(
226
+ type: Self.reading,
285
227
  properties: [.read, .notify],
286
228
  value: nil,
287
229
  permissions: [.readable]
288
230
  )
289
-
290
- let service = CBMutableService(type: serviceUUID, primary: true)
291
- service.characteristics = [characteristic]
292
- peripheralManager.add(service)
231
+ let service = CBMutableService(type: Self.service, primary: true)
232
+ service.characteristics = [reading]
233
+ manager.add(service)
293
234
  }
294
235
 
295
- func peripheralManager(
296
- _ peripheral: CBPeripheralManager,
297
- didAdd service: CBService,
298
- error: Error?
299
- ) {
236
+ func peripheralManager(_ manager: CBPeripheralManager, didAdd service: CBService, error: (any Error)?) {
300
237
  guard error == nil else { return }
301
- peripheralManager.startAdvertising([
302
- CBAdvertisementDataServiceUUIDsKey: [serviceUUID],
303
- CBAdvertisementDataLocalNameKey: "MyDevice"
238
+ manager.startAdvertising([
239
+ CBAdvertisementDataServiceUUIDsKey: [Self.service],
240
+ CBAdvertisementDataLocalNameKey: "Porch Thermometer",
304
241
  ])
305
242
  }
306
243
  }
307
244
  ```
308
245
 
309
- ## Background BLE
310
-
311
- ### Background Central Mode
246
+ Custom services use full 128-bit UUIDs created with `CBUUID(string:)`. Leaving
247
+ `value` nil makes the characteristic dynamic, so reads reach your delegate.
248
+ Answering read, write and subscription requests is shown in the
249
+ [patterns reference](references/ble-patterns.md).
312
250
 
313
- Add `bluetooth-central` to `UIBackgroundModes`. In the background:
251
+ ## Background operation
314
252
 
315
- - Scanning must specify one or more service UUIDs; `nil` scans are foreground-only
316
- - Scan options, including `CBCentralManagerScanOptionAllowDuplicatesKey`, have no effect
253
+ **Central (`bluetooth-central`):**
317
254
 
318
- ### Background Peripheral Mode
255
+ - Background scans must name service UUIDs; a `nil` scan runs only in the
256
+ foreground.
257
+ - Scan options, `CBCentralManagerScanOptionAllowDuplicatesKey` included, are
258
+ ignored in the background.
319
259
 
320
- Add `bluetooth-peripheral` to `UIBackgroundModes`. In the background:
260
+ **Peripheral (`bluetooth-peripheral`):**
321
261
 
322
- - Without this mode, published service contents are disabled while suspended
323
- - The local name is not advertised
324
- - Service UUIDs move to the overflow area and require explicit service scans
262
+ - Without this mode, the contents of published services are unavailable while
263
+ the app is suspended.
264
+ - In the background the local name is not advertised.
265
+ - Service UUIDs move to an overflow area that only centrals scanning
266
+ explicitly for those services can see.
325
267
 
326
- ## State Restoration
268
+ ## State restoration
327
269
 
328
- State restoration allows the system to re-create your central or peripheral
329
- manager after your app is terminated and relaunched for a BLE event.
330
-
331
- ### Central Manager State Restoration
270
+ If the app is terminated, the system can relaunch it for a Bluetooth event and
271
+ hand back the manager it had. Opt in with a restoration identifier:
332
272
 
333
273
  ```swift
334
- // 1. Create with a restoration identifier
335
- centralManager = CBCentralManager(
274
+ central = CBCentralManager(
336
275
  delegate: self,
337
276
  queue: nil,
338
- options: [CBCentralManagerOptionRestoreIdentifierKey: "myCentral"]
277
+ options: [CBCentralManagerOptionRestoreIdentifierKey: "scale-central"]
339
278
  )
340
279
 
341
- // 2. Implement the restoration delegate method
342
- func centralManager(
343
- _ central: CBCentralManager,
344
- willRestoreState dict: [String: Any]
345
- ) {
346
- if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey]
347
- as? [CBPeripheral] {
348
- for peripheral in peripherals {
349
- // Re-assign delegate and retain
350
- peripheral.delegate = self
351
- discoveredPeripheral = peripheral
280
+ func centralManager(_ central: CBCentralManager, willRestoreState state: [String: Any]) {
281
+ if let devices = state[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] {
282
+ for device in devices {
283
+ device.delegate = self
284
+ scale = device
352
285
  }
353
286
  }
354
- let restoredServices = dict[CBCentralManagerRestoredStateScanServicesKey]
355
- as? [CBUUID]
356
- let restoredOptions = dict[CBCentralManagerRestoredStateScanOptionsKey]
357
- as? [String: Any]
358
- // Resume scanning with restoredServices/restoredOptions if still needed.
359
- }
360
- ```
361
-
362
- ### Peripheral Manager State Restoration
363
-
364
- ```swift
365
- peripheralManager = CBPeripheralManager(
366
- delegate: self,
367
- queue: nil,
368
- options: [CBPeripheralManagerOptionRestoreIdentifierKey: "myPeripheral"]
369
- )
370
-
371
- func peripheralManager(
372
- _ peripheral: CBPeripheralManager,
373
- willRestoreState dict: [String: Any]
374
- ) {
375
- let services = dict[CBPeripheralManagerRestoredStateServicesKey]
376
- as? [CBMutableService]
377
- let advertisement = dict[CBPeripheralManagerRestoredStateAdvertisementDataKey]
378
- as? [String: Any]
379
- // Reconnect app state to restored services/advertisement as needed.
380
- }
381
- ```
382
-
383
- ## Common Mistakes
384
-
385
- ### DON'T: Scan or connect before poweredOn
386
-
387
- ```swift
388
- // WRONG: Scanning immediately -- manager may not be ready
389
- let manager = CBCentralManager(delegate: self, queue: nil)
390
- manager.scanForPeripherals(withServices: nil) // May silently fail
391
-
392
- // CORRECT: Wait for poweredOn in the delegate
393
- func centralManagerDidUpdateState(_ central: CBCentralManager) {
394
- if central.state == .poweredOn {
395
- central.scanForPeripherals(withServices: [serviceUUID])
396
- }
397
- }
398
- ```
399
-
400
- ### DON'T: Lose the peripheral reference
401
-
402
- Core Bluetooth does not retain discovered peripherals. If you don't hold a
403
- strong reference, the peripheral is deallocated and the connection fails silently.
404
-
405
- ```swift
406
- // WRONG: No strong reference kept
407
- func centralManager(_ central: CBCentralManager,
408
- didDiscover peripheral: CBPeripheral, ...) {
409
- central.connect(peripheral) // peripheral may be deallocated
410
- }
411
-
412
- // CORRECT: Retain the peripheral
413
- func centralManager(_ central: CBCentralManager,
414
- didDiscover peripheral: CBPeripheral, ...) {
415
- self.discoveredPeripheral = peripheral // Strong reference
416
- central.connect(peripheral)
417
- }
418
- ```
419
-
420
- ### DON'T: Scan for nil services in production
421
-
422
- ```swift
423
- // WRONG: Discovers every BLE device in range -- drains battery
424
- centralManager.scanForPeripherals(withServices: nil)
425
-
426
- // CORRECT: Specify the service UUIDs you need
427
- centralManager.scanForPeripherals(withServices: [targetServiceUUID])
428
- ```
429
-
430
- ### DON'T: Assume connection order or timing
431
-
432
- ```swift
433
- // WRONG: Assuming immediate connection
434
- centralManager.connect(peripheral)
435
- discoverServicesNow() // Peripheral not connected yet
436
-
437
- // CORRECT: Discover services in the didConnect callback
438
- func centralManager(_ central: CBCentralManager,
439
- didConnect peripheral: CBPeripheral) {
440
- peripheral.delegate = self
441
- peripheral.discoverServices([serviceUUID])
442
- }
443
- ```
444
-
445
- ### DON'T: Write without checking properties and flow control
446
-
447
- ```swift
448
- // WRONG: May fail, report an error, or provide no confirmation
449
- peripheral.writeValue(data, for: characteristic, type: .withResponse)
450
-
451
- // CORRECT: Check properties, length, and .withoutResponse flow control
452
- if characteristic.properties.contains(.write),
453
- data.count <= peripheral.maximumWriteValueLength(for: .withResponse) {
454
- peripheral.writeValue(data, for: characteristic, type: .withResponse)
455
- } else if characteristic.properties.contains(.writeWithoutResponse),
456
- peripheral.canSendWriteWithoutResponse,
457
- data.count <= peripheral.maximumWriteValueLength(for: .withoutResponse) {
458
- peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
287
+ let services = state[CBCentralManagerRestoredStateScanServicesKey] as? [CBUUID]
288
+ let options = state[CBCentralManagerRestoredStateScanOptionsKey] as? [String: Any]
289
+ resumeScanIfNeeded(services: services, options: options)
459
290
  }
460
291
  ```
461
292
 
462
- ## Review Checklist
463
-
464
- - [ ] `NSBluetoothAlwaysUsageDescription` added to Info.plist
465
- - [ ] All BLE operations gated on `centralManagerDidUpdateState` returning `.poweredOn`
466
- - [ ] Discovered peripherals retained with a strong reference
467
- - [ ] Scanning uses specific service UUIDs (not `nil`) in production
468
- - [ ] `CBPeripheralDelegate` set before calling `discoverServices`
469
- - [ ] Characteristic properties checked before read/write/notify
470
- - [ ] Write payloads stay within `maximumWriteValueLength(for:)`
471
- - [ ] `.withoutResponse` writes honor `canSendWriteWithoutResponse`
472
- - [ ] Background mode (`bluetooth-central` or `bluetooth-peripheral`) added if needed
473
- - [ ] State restoration identifier set if app needs relaunch-on-BLE-event support
474
- - [ ] `willRestoreState` delegate method implemented when using state restoration
475
- - [ ] Scanning stopped after discovering the target peripheral
476
- - [ ] Disconnection handled with optional automatic reconnect logic
477
- - [ ] Write type matches characteristic properties (`.withResponse` vs `.withoutResponse`)
293
+ A peripheral manager does the same with
294
+ `CBPeripheralManagerOptionRestoreIdentifierKey`, and its
295
+ `peripheralManager(_:willRestoreState:)` reads
296
+ `CBPeripheralManagerRestoredStateServicesKey` (an array of `CBMutableService`)
297
+ and `CBPeripheralManagerRestoredStateAdvertisementDataKey`.
298
+
299
+ ## Common mistakes
300
+
301
+ - **Acting before `.poweredOn`.** Scans and connects can fail without a word.
302
+ Start them from the state callback.
303
+ - **Dropping the peripheral.** Nothing else retains it, and the connection
304
+ quietly never happens.
305
+ - **`nil` service scans in production.** Heavy on battery; list the UUIDs.
306
+ - **Assuming the connection is instant.** Start service discovery in
307
+ `didConnect`, not right after `connect`.
308
+ - **Writing blind.** Ignoring properties, the maximum write length or
309
+ `.withoutResponse` flow control leads to failed or unconfirmed writes.
310
+
311
+ ## Review checklist
312
+
313
+ - [ ] `NSBluetoothAlwaysUsageDescription` is present
314
+ - [ ] Every operation waits for `.poweredOn`
315
+ - [ ] Discovered peripherals are held strongly
316
+ - [ ] Production scans list specific service UUIDs
317
+ - [ ] `CBPeripheralDelegate` is set before `discoverServices`
318
+ - [ ] Properties are checked before read, write or notify
319
+ - [ ] Writes fit within `maximumWriteValueLength(for:)`
320
+ - [ ] `.withoutResponse` writes respect `canSendWriteWithoutResponse`
321
+ - [ ] Needed background modes are declared
322
+ - [ ] A restoration identifier is set when relaunch on BLE events matters
323
+ - [ ] `willRestoreState` is implemented whenever restoration is on
324
+ - [ ] Scanning stops once the target is found
325
+ - [ ] Disconnects are handled, with reconnection where it makes sense
326
+ - [ ] Write type matches the characteristic's properties
478
327
 
479
328
  ## References
480
329
 
481
- - Extended patterns (reconnection strategies, data parsing, SwiftUI integration): [references/ble-patterns.md](references/ble-patterns.md)
482
- - [Core Bluetooth framework](https://sosumi.ai/documentation/corebluetooth)
483
- - [CBCentralManager](https://sosumi.ai/documentation/corebluetooth/cbcentralmanager)
484
- - [CBPeripheral](https://sosumi.ai/documentation/corebluetooth/cbperipheral)
485
- - [CBPeripheralManager](https://sosumi.ai/documentation/corebluetooth/cbperipheralmanager)
486
- - [CBService](https://sosumi.ai/documentation/corebluetooth/cbservice)
487
- - [CBCharacteristic](https://sosumi.ai/documentation/corebluetooth/cbcharacteristic)
488
- - [CBUUID](https://sosumi.ai/documentation/corebluetooth/cbuuid)
489
- - [CBCentralManagerDelegate](https://sosumi.ai/documentation/corebluetooth/cbcentralmanagerdelegate)
490
- - [CBPeripheralDelegate](https://sosumi.ai/documentation/corebluetooth/cbperipheraldelegate)
491
- - [NSBluetoothAlwaysUsageDescription](https://sosumi.ai/documentation/bundleresources/information-property-list/nsbluetoothalwaysusagedescription)
492
- - [CBManagerAuthorization](https://sosumi.ai/documentation/corebluetooth/cbmanagerauthorization)
493
- - [scanForPeripherals(withServices:options:)](https://sosumi.ai/documentation/corebluetooth/cbcentralmanager/scanforperipherals(withservices:options:))
494
- - [startAdvertising(_:)](https://sosumi.ai/documentation/corebluetooth/cbperipheralmanager/startadvertising(_:))
495
- - [writeValue(_:for:type:)](https://sosumi.ai/documentation/corebluetooth/cbperipheral/writevalue(_:for:type:))
496
- - [maximumWriteValueLength(for:)](https://sosumi.ai/documentation/corebluetooth/cbperipheral/maximumwritevaluelength(for:))
497
- - [canSendWriteWithoutResponse](https://sosumi.ai/documentation/corebluetooth/cbperipheral/cansendwritewithoutresponse)
498
- - [Configuring background execution modes](https://sosumi.ai/documentation/xcode/configuring-background-execution-modes)
330
+ - [BLE patterns](references/ble-patterns.md): SwiftUI manager and view,
331
+ reconnection, byte parsing, write flow control, several peripherals, L2CAP,
332
+ answering requests as a peripheral
333
+ - Apple documentation (developer.apple.com):
334
+ [framework](https://developer.apple.com/documentation/corebluetooth),
335
+ [central manager](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager),
336
+ [peripheral](https://developer.apple.com/documentation/corebluetooth/cbperipheral),
337
+ [peripheral manager](https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager),
338
+ [service](https://developer.apple.com/documentation/corebluetooth/cbservice),
339
+ [characteristic](https://developer.apple.com/documentation/corebluetooth/cbcharacteristic),
340
+ [UUID](https://developer.apple.com/documentation/corebluetooth/cbuuid),
341
+ [central delegate](https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate),
342
+ [peripheral delegate](https://developer.apple.com/documentation/corebluetooth/cbperipheraldelegate),
343
+ [usage description key](https://developer.apple.com/documentation/bundleresources/information-property-list/nsbluetoothalwaysusagedescription),
344
+ [authorization values](https://developer.apple.com/documentation/corebluetooth/cbmanagerauthorization),
345
+ [scanning](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/scanforperipherals(withservices:options:)),
346
+ [advertising](https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/startadvertising(_:)),
347
+ [writing values](https://developer.apple.com/documentation/corebluetooth/cbperipheral) (`writeValue(_:for:type:)` on the peripheral page),
348
+ [maximum write length](https://developer.apple.com/documentation/corebluetooth/cbperipheral/maximumwritevaluelength(for:)),
349
+ [write-without-response readiness](https://developer.apple.com/documentation/corebluetooth/cbperipheral/cansendwritewithoutresponse),
350
+ [background execution modes](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes)