@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,78 +1,72 @@
1
- # PhotosPicker Patterns
1
+ # Picker and PhotoKit recipes
2
2
 
3
- Complete recipes for photo and video selection, loading, saving, and processing. All patterns target iOS 16+ with SwiftUI and async/await.
4
-
5
- ---
3
+ Each recipe uses SwiftUI with async/await and needs iOS 17 or later (the views
4
+ use the two-parameter `onChange(of:)`). `PhotosPicker` itself is iOS 16.
6
5
 
7
6
  ## Contents
8
7
 
9
- - [1. Single Photo Selection with Loading State](#1-single-photo-selection-with-loading-state)
10
- - [2. Multi-Selection with Progress Tracking](#2-multi-selection-with-progress-tracking)
11
- - [3. Loading Videos from PhotosPicker](#3-loading-videos-from-photospicker)
12
- - [4. Loading Live Photos](#4-loading-live-photos)
13
- - [5. PHPickerViewController Wrapping (UIKit Interop)](#5-phpickerviewcontroller-wrapping-uikit-interop)
14
- - [6. Saving Images to Photo Library](#6-saving-images-to-photo-library)
15
- - [7. Thumbnail Generation with ImageIO Downsampling](#7-thumbnail-generation-with-imageio-downsampling)
16
- - [8. HEIC/HEIF Handling](#8-heicheif-handling)
17
- - [9. Custom PhotosPicker Appearance](#9-custom-photospicker-appearance)
18
- - [10. Image Cropping Pattern](#10-image-cropping-pattern)
8
+ - [1. One photo with an explicit loading state](#1-one-photo-with-an-explicit-loading-state)
9
+ - [2. Many photos with progress](#2-many-photos-with-progress)
10
+ - [3. Video from the picker](#3-video-from-the-picker)
11
+ - [4. Live Photos](#4-live-photos)
12
+ - [5. PHPickerViewController inside SwiftUI](#5-phpickerviewcontroller-inside-swiftui)
13
+ - [6. Saving into the library](#6-saving-into-the-library)
14
+ - [7. Thumbnails with ImageIO](#7-thumbnails-with-imageio)
15
+ - [8. HEIC and HEIF](#8-heic-and-heif)
16
+ - [9. Picker appearance](#9-picker-appearance)
17
+ - [10. Cropping](#10-cropping)
19
18
 
20
- ## 1. Single Photo Selection with Loading State
19
+ ## 1. One photo with an explicit loading state
21
20
 
22
- Handle the full lifecycle: idle, loading, loaded, and error.
21
+ Model what the screen shows as a small enum instead of a pile of optionals.
23
22
 
24
23
  ```swift
25
24
  import SwiftUI
26
25
  import PhotosUI
27
26
 
28
- struct ProfilePhotoPicker: View {
29
- @State private var selectedItem: PhotosPickerItem?
30
- @State private var loadState: LoadState = .idle
31
-
32
- enum LoadState {
33
- case idle, loading, loaded(Image), error(String)
27
+ struct AvatarEditor: View {
28
+ enum Stage {
29
+ case empty
30
+ case working
31
+ case ready(Image)
32
+ case failed(String)
34
33
  }
35
34
 
35
+ @State private var pick: PhotosPickerItem?
36
+ @State private var stage: Stage = .empty
37
+
36
38
  var body: some View {
37
- VStack {
38
- switch loadState {
39
- case .idle:
40
- Image(systemName: "person.crop.circle.fill")
41
- .resizable()
42
- .frame(width: 120, height: 120)
43
- .foregroundStyle(.secondary)
44
- case .loading:
39
+ VStack(spacing: 16) {
40
+ switch stage {
41
+ case .empty:
42
+ Label("No avatar", systemImage: "person.crop.circle.fill")
43
+ .labelStyle(.iconOnly)
44
+ .font(.system(size: 96))
45
+ case .working:
45
46
  ProgressView()
46
- .frame(width: 120, height: 120)
47
- case .loaded(let image):
48
- image
49
- .resizable()
50
- .scaledToFill()
51
- .frame(width: 120, height: 120)
52
- .clipShape(Circle())
53
- case .error(let message):
54
- ContentUnavailableView(message, systemImage: "exclamationmark.triangle")
47
+ case .ready(let face):
48
+ face.resizable().aspectRatio(contentMode: .fill)
49
+ .frame(width: 120, height: 120).clipShape(.circle)
50
+ case .failed(let reason):
51
+ ContentUnavailableView("Could Not Load", systemImage: "exclamationmark.triangle",
52
+ description: Text(reason))
55
53
  }
56
-
57
- PhotosPicker("Choose Photo", selection: $selectedItem, matching: .images)
54
+ PhotosPicker("Change Avatar", selection: $pick, matching: .images)
58
55
  .buttonStyle(.borderedProminent)
59
56
  }
60
- .onChange(of: selectedItem) { _, newItem in
61
- guard let newItem else {
62
- loadState = .idle
63
- return
64
- }
65
- loadState = .loading
57
+ .onChange(of: pick) { _, chosen in
58
+ guard let chosen else { stage = .empty; return }
59
+ stage = .working
66
60
  Task {
67
61
  do {
68
- if let data = try await newItem.loadTransferable(type: Data.self),
69
- let uiImage = UIImage(data: data) {
70
- loadState = .loaded(Image(uiImage: uiImage))
71
- } else {
72
- loadState = .error("Unable to load image")
62
+ guard let raw = try await chosen.loadTransferable(type: Data.self),
63
+ let decoded = UIImage(data: raw) else {
64
+ stage = .failed("That file is not an image this app can read.")
65
+ return
73
66
  }
67
+ stage = .ready(Image(uiImage: decoded))
74
68
  } catch {
75
- loadState = .error(error.localizedDescription)
69
+ stage = .failed(error.localizedDescription)
76
70
  }
77
71
  }
78
72
  }
@@ -80,518 +74,378 @@ struct ProfilePhotoPicker: View {
80
74
  }
81
75
  ```
82
76
 
83
- ---
84
-
85
- ## 2. Multi-Selection with Progress Tracking
77
+ A cleared selection goes back to `.empty`. A `nil` payload or an undecodable
78
+ one becomes a readable failure; a thrown error shows its
79
+ `localizedDescription`.
86
80
 
87
- Load multiple images sequentially and report progress back to the UI.
81
+ ## 2. Many photos with progress
88
82
 
89
83
  ```swift
90
- import SwiftUI
91
- import PhotosUI
92
-
93
- struct MultiPhotoLoader: View {
94
- @State private var selectedItems: [PhotosPickerItem] = []
95
- @State private var loadedImages: [UIImage] = []
96
- @State private var loadProgress: Double = 0
97
- @State private var isLoading = false
84
+ struct AlbumImporter: View {
85
+ @State private var picks: [PhotosPickerItem] = []
86
+ @State private var imported: [Image] = []
87
+ @State private var fraction = 0.0
88
+ @State private var busy = false
98
89
 
99
90
  var body: some View {
100
91
  VStack {
101
- if isLoading {
102
- ProgressView(value: loadProgress)
103
- .padding()
104
- }
105
-
92
+ PhotosPicker("Import", selection: $picks, maxSelectionCount: 10, matching: .images)
93
+ if busy { ProgressView(value: fraction) }
106
94
  ScrollView(.horizontal) {
107
95
  LazyHStack {
108
- ForEach(loadedImages.indices, id: \.self) { index in
109
- Image(uiImage: loadedImages[index])
110
- .resizable()
111
- .scaledToFill()
112
- .frame(width: 100, height: 100)
113
- .clipShape(.rect(cornerRadius: 8))
96
+ ForEach(imported.indices, id: \.self) { i in
97
+ imported[i].resizable().scaledToFill().frame(width: 108, height: 108).clipped()
114
98
  }
115
99
  }
116
- .padding(.horizontal)
117
100
  }
118
- .frame(height: loadedImages.isEmpty ? 0 : 116)
119
-
120
- PhotosPicker(
121
- "Select Photos",
122
- selection: $selectedItems,
123
- maxSelectionCount: 10,
124
- matching: .images
125
- )
101
+ .frame(height: imported.isEmpty ? 0 : 116)
126
102
  }
127
- .onChange(of: selectedItems) { _, newItems in
128
- Task { await loadImages(from: newItems) }
103
+ .onChange(of: picks) { _, batch in
104
+ Task { await importSequentially(batch) }
129
105
  }
130
106
  }
131
107
 
132
- private func loadImages(from items: [PhotosPickerItem]) async {
133
- isLoading = true
134
- loadProgress = 0
135
- var images: [UIImage] = []
136
-
137
- for (index, item) in items.enumerated() {
138
- if let data = try? await item.loadTransferable(type: Data.self),
139
- let uiImage = UIImage(data: data) {
140
- images.append(uiImage)
108
+ private func importSequentially(_ batch: [PhotosPickerItem]) async {
109
+ busy = true
110
+ var collected: [Image] = []
111
+ for (position, entry) in batch.enumerated() {
112
+ if let raw = try? await entry.loadTransferable(type: Data.self),
113
+ let decoded = UIImage(data: raw) {
114
+ collected.append(Image(uiImage: decoded))
141
115
  }
142
- loadProgress = Double(index + 1) / Double(items.count)
116
+ fraction = Double(position + 1) / Double(batch.count)
143
117
  }
144
-
145
- loadedImages = images
146
- isLoading = false
118
+ imported = collected
119
+ busy = false
147
120
  }
148
121
  }
149
122
  ```
150
123
 
151
- Load sequentially rather than concurrently to control memory usage. Each full-resolution image can be large; loading ten simultaneously risks memory termination.
124
+ Load one item after another. Ten full-resolution decodes running at the same
125
+ time can push the app past its memory limit and get it terminated.
152
126
 
153
- ---
127
+ ## 3. Video from the picker
154
128
 
155
- ## 3. Loading Videos from PhotosPicker
156
-
157
- Use a `Transferable` wrapper that writes video data to a temporary file, since videos are too large to hold in memory as `Data`.
129
+ Movies are too large to hold as `Data`. Import them as a file and copy that
130
+ file somewhere the app owns.
158
131
 
159
132
  ```swift
160
- import SwiftUI
161
- import PhotosUI
162
133
  import AVKit
134
+ import CoreTransferable
163
135
 
164
- struct PickedMovie: Transferable {
165
- let url: URL
136
+ struct PickedClip: Transferable {
137
+ let location: URL
166
138
 
167
139
  static var transferRepresentation: some TransferRepresentation {
168
- FileRepresentation(contentType: .movie) { movie in
169
- SentTransferredFile(movie.url)
170
- } importing: { received in
171
- // Copy to a temporary location the app controls
172
- let tempURL = FileManager.default.temporaryDirectory
140
+ FileRepresentation(contentType: .movie) { clip in
141
+ SentTransferredFile(clip.location)
142
+ } importing: { incoming in
143
+ let copy = URL.temporaryDirectory
173
144
  .appendingPathComponent(UUID().uuidString)
174
145
  .appendingPathExtension("mov")
175
- try FileManager.default.copyItem(at: received.file, to: tempURL)
176
- return PickedMovie(url: tempURL)
146
+ try FileManager.default.copyItem(at: incoming.file, to: copy)
147
+ return PickedClip(location: copy)
177
148
  }
178
149
  }
179
150
  }
180
151
 
181
- struct VideoPickerView: View {
182
- @State private var selectedItem: PhotosPickerItem?
183
- @State private var player: AVPlayer?
152
+ struct ClipPreview: View {
153
+ @State private var pick: PhotosPickerItem?
154
+ @State private var clipPlayer: AVPlayer?
184
155
 
185
156
  var body: some View {
186
157
  VStack {
187
- if let player {
188
- VideoPlayer(player: player)
189
- .frame(height: 300)
158
+ if let clipPlayer {
159
+ VideoPlayer(player: clipPlayer).frame(height: 280)
190
160
  }
191
-
192
- PhotosPicker("Select Video", selection: $selectedItem, matching: .videos)
161
+ PhotosPicker("Pick Clip", selection: $pick, matching: .videos)
193
162
  }
194
- .onChange(of: selectedItem) { _, newItem in
163
+ .onChange(of: pick) { _, chosen in
195
164
  Task {
196
- if let movie = try? await newItem?.loadTransferable(type: PickedMovie.self) {
197
- player = AVPlayer(url: movie.url)
198
- }
165
+ guard let clip = try? await chosen?.loadTransferable(type: PickedClip.self) else { return }
166
+ clipPlayer = AVPlayer(url: clip.location)
199
167
  }
200
168
  }
201
169
  }
202
170
  }
203
171
  ```
204
172
 
205
- Always copy the received file to a temporary directory you control. The system may delete the original transfer file at any time.
173
+ The file handed to `importing:` is temporary and the system may remove it at
174
+ any time, so copy it before returning.
206
175
 
207
- ---
176
+ ## 4. Live Photos
208
177
 
209
- ## 4. Loading Live Photos
178
+ `PHLivePhoto` conforms to `Transferable` through PhotosUI starting with iOS 16,
179
+ so a Live Photo loads like any other type. Display it with `PHLivePhotoView`
180
+ wrapped for SwiftUI.
210
181
 
211
182
  ```swift
212
- import SwiftUI
213
- import PhotosUI
183
+ struct LiveMoment: UIViewRepresentable {
184
+ let moment: PHLivePhoto
214
185
 
215
- @available(iOS 17.0, *)
216
- struct LivePhotoPickerView: View {
217
- @State private var selectedItem: PhotosPickerItem?
218
- @State private var livePhoto: PHLivePhoto?
186
+ func makeUIView(context: Context) -> PHLivePhotoView {
187
+ let view = PHLivePhotoView()
188
+ view.contentMode = .scaleAspectFit
189
+ return view
190
+ }
191
+
192
+ func updateUIView(_ view: PHLivePhotoView, context: Context) {
193
+ view.livePhoto = moment
194
+ }
195
+ }
196
+
197
+ struct LiveMomentPicker: View {
198
+ @State private var pick: PhotosPickerItem?
199
+ @State private var moment: PHLivePhoto?
219
200
 
220
201
  var body: some View {
221
202
  VStack {
222
- if let livePhoto {
223
- LivePhotoView(livePhoto: livePhoto)
224
- .frame(height: 300)
225
- }
226
-
227
- PhotosPicker(
228
- "Select Live Photo",
229
- selection: $selectedItem,
230
- matching: .livePhotos
231
- )
203
+ if let moment { LiveMoment(moment: moment).frame(height: 320) }
204
+ PhotosPicker("Pick Live Photo", selection: $pick, matching: .livePhotos)
232
205
  }
233
- .onChange(of: selectedItem) { _, newItem in
234
- Task {
235
- livePhoto = try? await newItem?.loadTransferable(type: PHLivePhoto.self)
236
- }
206
+ .onChange(of: pick) { _, chosen in
207
+ Task { moment = try? await chosen?.loadTransferable(type: PHLivePhoto.self) }
237
208
  }
238
209
  }
239
210
  }
240
211
  ```
241
212
 
242
- `PHLivePhoto` conforms to `Transferable` on iOS 17+. On iOS 16, load the Live Photo components manually using `PHAsset`.
243
-
244
- ---
245
-
246
- ## 5. PHPickerViewController Wrapping (UIKit Interop)
213
+ ## 5. PHPickerViewController inside SwiftUI
247
214
 
248
- Use `PHPickerViewController` when you need UIKit-level control or are integrating into an existing UIKit codebase. Prefer `PhotosPicker` for pure SwiftUI apps.
215
+ Reach for `PHPickerViewController` when the surrounding code is UIKit or needs
216
+ UIKit-level control. In pure SwiftUI, `PhotosPicker` is simpler.
249
217
 
250
218
  ```swift
251
- import SwiftUI
252
- import PhotosUI
253
-
254
- struct PHPickerWrapper: UIViewControllerRepresentable {
255
- @Binding var selectedImages: [UIImage]
256
- var selectionLimit: Int = 0
257
- @Environment(\.dismiss) private var dismiss
258
-
259
- func makeCoordinator() -> Coordinator { Coordinator(self) }
219
+ struct LegacyGalleryPicker: UIViewControllerRepresentable {
220
+ @Binding var chosen: [UIImage]
221
+ var limit = 0
260
222
 
261
223
  func makeUIViewController(context: Context) -> PHPickerViewController {
262
- var config = PHPickerConfiguration(photoLibrary: .shared())
263
- config.filter = .images
264
- config.selectionLimit = selectionLimit
265
- config.preferredAssetRepresentationMode = .current
266
-
267
- let picker = PHPickerViewController(configuration: config)
224
+ var setup = PHPickerConfiguration(photoLibrary: .shared())
225
+ setup.filter = .images
226
+ setup.selectionLimit = limit
227
+ setup.preferredAssetRepresentationMode = .current
228
+ let picker = PHPickerViewController(configuration: setup)
268
229
  picker.delegate = context.coordinator
269
230
  return picker
270
231
  }
271
232
 
272
- func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
273
- // Configuration is immutable after creation
274
- }
233
+ func updateUIViewController(_ controller: PHPickerViewController, context: Context) {}
275
234
 
276
- final class Coordinator: NSObject, PHPickerViewControllerDelegate {
277
- let parent: PHPickerWrapper
235
+ func makeCoordinator() -> Coordinator { Coordinator(owner: self) }
278
236
 
279
- init(_ parent: PHPickerWrapper) { self.parent = parent }
280
-
281
- func picker(
282
- _ picker: PHPickerViewController,
283
- didFinishPicking results: [PHPickerResult]
284
- ) {
285
- parent.dismiss()
286
- guard !results.isEmpty else { return }
237
+ final class Coordinator: NSObject, PHPickerViewControllerDelegate {
238
+ let owner: LegacyGalleryPicker
239
+ init(owner: LegacyGalleryPicker) { self.owner = owner }
287
240
 
241
+ func picker(_ sheet: PHPickerViewController, didFinishPicking picks: [PHPickerResult]) {
242
+ sheet.dismiss(animated: true)
243
+ let providers = picks.map(\.itemProvider)
288
244
  Task { @MainActor in
289
245
  var images: [UIImage] = []
290
- for result in results {
291
- if let image = await loadImage(from: result.itemProvider) {
292
- images.append(image)
246
+ for provider in providers where provider.canLoadObject(ofClass: UIImage.self) {
247
+ let image: UIImage? = await withCheckedContinuation { done in
248
+ provider.loadObject(ofClass: UIImage.self) { object, _ in
249
+ done.resume(returning: object as? UIImage)
250
+ }
293
251
  }
252
+ if let image { images.append(image) }
294
253
  }
295
- parent.selectedImages = images
296
- }
297
- }
298
-
299
- private func loadImage(from provider: NSItemProvider) async -> UIImage? {
300
- await withCheckedContinuation { continuation in
301
- if provider.canLoadObject(ofClass: UIImage.self) {
302
- provider.loadObject(ofClass: UIImage.self) { image, _ in
303
- continuation.resume(returning: image as? UIImage)
304
- }
305
- } else {
306
- continuation.resume(returning: nil)
307
- }
254
+ owner.chosen = images
308
255
  }
309
256
  }
310
257
  }
311
258
  }
312
259
  ```
313
260
 
314
- Always call `parent.dismiss()` in `picker(_:didFinishPicking:)` -- this delegate method fires for both selection and cancellation (with empty results).
261
+ - A `selectionLimit` of 0 means the system maximum.
262
+ - The configuration is fixed once the controller exists, which is why
263
+ `updateUIViewController` does nothing.
264
+ - `picker(_:didFinishPicking:)` fires for Cancel too, with an empty array.
265
+ Dismiss the picker there in every case.
315
266
 
316
- ---
267
+ ## 6. Saving into the library
317
268
 
318
- ## 6. Saving Images to Photo Library
319
-
320
- Saving requires `NSPhotoLibraryAddUsageDescription` in Info.plist. Use `PHPhotoLibrary` for saving with metadata, or `UIImageWriteToSavedPhotosAlbum` for simple saves.
321
-
322
- ### Using PHPhotoLibrary (Preferred)
269
+ Saving needs `NSPhotoLibraryAddUsageDescription`. Use `PHPhotoLibrary` when you
270
+ want control over the stored data and metadata;
271
+ `UIImageWriteToSavedPhotosAlbum` is enough for a quick one-off save.
323
272
 
324
273
  ```swift
325
274
  import Photos
326
275
 
327
- func saveImageToLibrary(_ image: UIImage) async throws {
328
- try await PHPhotoLibrary.shared().performChanges {
329
- let request = PHAssetCreationRequest.forAsset()
330
- request.addResource(with: .photo, data: image.jpegData(compressionQuality: 0.9)!, options: nil)
276
+ enum LibraryWriter {
277
+ static func store(_ image: UIImage) async throws {
278
+ guard let encoded = image.jpegData(compressionQuality: 0.9) else { return }
279
+ try await store(encoded)
331
280
  }
332
- }
333
281
 
334
- func saveImageDataToLibrary(_ data: Data) async throws {
335
- try await PHPhotoLibrary.shared().performChanges {
336
- let request = PHAssetCreationRequest.forAsset()
337
- let options = PHAssetResourceCreationOptions()
338
- request.addResource(with: .photo, data: data, options: options)
282
+ static func store(_ encoded: Data) async throws {
283
+ try await PHPhotoLibrary.shared().performChanges {
284
+ let request = PHAssetCreationRequest.forAsset()
285
+ request.addResource(with: .photo, data: encoded, options: PHAssetResourceCreationOptions())
286
+ }
339
287
  }
340
288
  }
341
289
  ```
342
290
 
343
- ### Saving to a Specific Album
344
-
345
- ```swift
346
- import Photos
347
-
348
- func saveToAlbum(image: UIImage, albumName: String) async throws {
349
- let album = try await findOrCreateAlbum(named: albumName)
291
+ ### Into a named album
350
292
 
351
- try await PHPhotoLibrary.shared().performChanges {
352
- let assetRequest = PHAssetCreationRequest.forAsset()
353
- assetRequest.addResource(
354
- with: .photo,
355
- data: image.jpegData(compressionQuality: 0.9)!,
356
- options: nil
357
- )
293
+ Look the album up by title; if it is missing, create it and capture its
294
+ placeholder, then add the new asset in the same change block. The fallback is an
295
+ `if`, not `??`: the right side of `??` is an autoclosure and cannot `await`.
358
296
 
359
- guard let placeholder = assetRequest.placeholderForCreatedAsset,
360
- let albumChangeRequest = PHAssetCollectionChangeRequest(for: album) else { return }
361
- albumChangeRequest.addAssets([placeholder] as NSArray)
362
- }
363
- }
364
-
365
- private func findOrCreateAlbum(named name: String) async throws -> PHAssetCollection {
366
- let fetchOptions = PHFetchOptions()
367
- fetchOptions.predicate = NSPredicate(format: "title = %@", name)
368
- let result = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
297
+ ```swift
298
+ enum AlbumWriter {
299
+ enum Failure: Error { case albumMissing }
369
300
 
370
- if let existing = result.firstObject {
371
- return existing
301
+ static func album(titled title: String) -> PHAssetCollection? {
302
+ let query = PHFetchOptions()
303
+ query.predicate = NSPredicate(format: "title = %@", title)
304
+ return PHAssetCollection.fetchAssetCollections(with: PHAssetCollectionType.album, subtype: .any, options: query).firstObject
372
305
  }
373
306
 
374
- // Create the album
375
- var placeholder: PHObjectPlaceholder?
376
- try await PHPhotoLibrary.shared().performChanges {
377
- let request = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: name)
378
- placeholder = request.placeholderForCreatedAssetCollection
307
+ static func makeAlbum(titled title: String) async throws -> PHAssetCollection {
308
+ var marker: PHObjectPlaceholder?
309
+ try await PHPhotoLibrary.shared().performChanges {
310
+ let create = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: title)
311
+ marker = create.placeholderForCreatedAssetCollection
312
+ }
313
+ guard let id = marker?.localIdentifier,
314
+ let made = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [id], options: nil).firstObject
315
+ else { throw Failure.albumMissing }
316
+ return made
379
317
  }
380
318
 
381
- guard let placeholder,
382
- let collection = PHAssetCollection.fetchAssetCollections(
383
- withLocalIdentifiers: [placeholder.localIdentifier],
384
- options: nil
385
- ).firstObject else {
386
- throw PhotoLibraryError.albumCreationFailed
319
+ static func save(_ encoded: Data, into title: String) async throws {
320
+ let target: PHAssetCollection
321
+ if let existing = album(titled: title) {
322
+ target = existing
323
+ } else {
324
+ target = try await makeAlbum(titled: title)
325
+ }
326
+ try await PHPhotoLibrary.shared().performChanges {
327
+ let request = PHAssetCreationRequest.forAsset()
328
+ request.addResource(with: .photo, data: encoded, options: nil)
329
+ guard let newAsset = request.placeholderForCreatedAsset,
330
+ let edit = PHAssetCollectionChangeRequest(for: target) else { return }
331
+ edit.addAssets([newAsset] as NSArray)
332
+ }
387
333
  }
388
-
389
- return collection
390
- }
391
-
392
- enum PhotoLibraryError: Error {
393
- case albumCreationFailed
394
334
  }
395
335
  ```
396
336
 
397
- ---
337
+ ## 7. Thumbnails with ImageIO
398
338
 
399
- ## 7. Thumbnail Generation with ImageIO Downsampling
400
-
401
- Generate thumbnails efficiently without decoding the full image into memory. This is critical for grids and lists displaying many photos.
339
+ Build thumbnails without decoding the whole image; with a grid of hundreds of
340
+ photos this matters more than anything else. `UIImage(data:)` followed by a
341
+ resize still decodes the full bitmap first, so prefer downsampling. When the
342
+ image is on disk, open it by URL with caching switched off so the file is not
343
+ read into memory up front:
402
344
 
403
345
  ```swift
404
346
  import ImageIO
405
- import UIKit
406
-
407
- /// Downsample image data to a target display size.
408
- /// Use this instead of UIImage(data:) followed by resizing, which decodes the full image.
409
- func downsample(
410
- data: Data,
411
- to pointSize: CGSize,
412
- scale: CGFloat = UITraitCollection.current.displayScale
413
- ) -> UIImage? {
414
- let maxDimension = max(pointSize.width, pointSize.height) * scale
415
-
416
- let options: [CFString: Any] = [
417
- kCGImageSourceCreateThumbnailFromImageAlways: true,
418
- kCGImageSourceShouldCacheImmediately: true,
419
- kCGImageSourceCreateThumbnailWithTransform: true,
420
- kCGImageSourceThumbnailMaxPixelSize: maxDimension
421
- ]
422
347
 
423
- guard let source = CGImageSourceCreateWithData(data as CFData, nil),
424
- let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else {
425
- return nil
426
- }
427
-
428
- return UIImage(cgImage: cgImage)
429
- }
430
-
431
- /// Downsample from a file URL (avoids loading full data into memory).
432
- func downsample(
433
- url: URL,
434
- to pointSize: CGSize,
435
- scale: CGFloat = UITraitCollection.current.displayScale
436
- ) -> UIImage? {
437
- let maxDimension = max(pointSize.width, pointSize.height) * scale
438
-
439
- let sourceOptions: [CFString: Any] = [kCGImageSourceShouldCache: false]
440
- guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions as CFDictionary) else {
441
- return nil
442
- }
443
-
444
- let downsampleOptions: [CFString: Any] = [
348
+ func fileThumbnail(at file: URL, longestPixelEdge: CGFloat) -> UIImage? {
349
+ let openOptions = [kCGImageSourceShouldCache: false] as CFDictionary
350
+ guard let reader = CGImageSourceCreateWithURL(file as CFURL, openOptions) else { return nil }
351
+ let shrink: [CFString: Any] = [
445
352
  kCGImageSourceCreateThumbnailFromImageAlways: true,
446
- kCGImageSourceShouldCacheImmediately: true,
447
353
  kCGImageSourceCreateThumbnailWithTransform: true,
448
- kCGImageSourceThumbnailMaxPixelSize: maxDimension
354
+ kCGImageSourceShouldCacheImmediately: true,
355
+ kCGImageSourceThumbnailMaxPixelSize: longestPixelEdge,
449
356
  ]
450
-
451
- guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions as CFDictionary) else {
452
- return nil
453
- }
454
-
455
- return UIImage(cgImage: cgImage)
357
+ return CGImageSourceCreateThumbnailAtIndex(reader, 0, shrink as CFDictionary).map(UIImage.init(cgImage:))
456
358
  }
457
- ```
458
-
459
- ### Usage in a Photo Grid
460
359
 
461
- ```swift
462
- struct PhotoGridItem: View {
463
- let imageData: Data
464
- let thumbnailSize = CGSize(width: 100, height: 100)
465
-
466
- @State private var thumbnail: UIImage?
360
+ struct GridCell: View {
361
+ let file: URL
362
+ @Environment(\.displayScale) private var scale
363
+ @State private var small: UIImage?
467
364
 
468
365
  var body: some View {
469
- Group {
470
- if let thumbnail {
471
- Image(uiImage: thumbnail)
472
- .resizable()
473
- .scaledToFill()
474
- } else {
475
- Rectangle()
476
- .fill(.quaternary)
477
- .overlay(ProgressView())
366
+ Rectangle()
367
+ .fill(.quaternary)
368
+ .overlay {
369
+ if let small { Image(uiImage: small).resizable().scaledToFill() } else { ProgressView() }
478
370
  }
479
- }
480
- .frame(width: 100, height: 100)
481
- .clipShape(.rect(cornerRadius: 6))
482
- .task {
483
- thumbnail = downsample(data: imageData, to: thumbnailSize)
484
- }
371
+ .frame(width: 100, height: 100)
372
+ .clipShape(.rect(cornerRadius: 6))
373
+ .task { small = fileThumbnail(at: file, longestPixelEdge: 100 * scale) }
485
374
  }
486
375
  }
487
376
  ```
488
377
 
489
- ---
378
+ ## 8. HEIC and HEIF
490
379
 
491
- ## 8. HEIC/HEIF Handling
492
-
493
- Modern iPhones capture photos in HEIC format by default. Handle both HEIC and JPEG transparently.
494
-
495
- ### Checking Image Format
380
+ Recent iPhones capture HEIC unless the user changed the setting, so code must
381
+ accept HEIC and JPEG alike.
496
382
 
497
383
  ```swift
498
384
  import UniformTypeIdentifiers
499
385
 
500
- func imageContentType(data: Data) -> UTType? {
501
- guard let source = CGImageSourceCreateWithData(data as CFData, nil),
502
- let uti = CGImageSourceGetType(source) as? String else {
503
- return nil
504
- }
505
- return UTType(uti)
386
+ func detectedType(of bytes: Data) -> UTType? {
387
+ guard let reader = CGImageSourceCreateWithData(bytes as CFData, nil),
388
+ let identifier = CGImageSourceGetType(reader) else { return nil }
389
+ return UTType(identifier as String)
506
390
  }
507
- ```
508
-
509
- ### Converting HEIC to JPEG
510
-
511
- ```swift
512
- import UIKit
513
391
 
514
- func convertToJPEG(heicData: Data, compressionQuality: CGFloat = 0.9) -> Data? {
515
- guard let image = UIImage(data: heicData) else { return nil }
516
- return image.jpegData(compressionQuality: compressionQuality)
392
+ func jpegCopy(of heic: Data, quality: CGFloat = 0.9) -> Data? {
393
+ UIImage(data: heic)?.jpegData(compressionQuality: quality)
517
394
  }
518
395
  ```
519
396
 
520
- ### Preserving HEIC When Possible
397
+ - Keep HEIC when saving or uploading: better quality per byte. Convert only
398
+ when the receiver demands JPEG, such as a server that rejects HEIC.
399
+ - On `PHPickerConfiguration`, `preferredAssetRepresentationMode = .current`
400
+ avoids transcoding and hands over the HEIC as stored. `.compatible` asks for
401
+ the most compatible representation and transcodes when needed, which for
402
+ HEIC photos usually means JPEG.
521
403
 
522
- When saving or uploading, prefer keeping the original HEIC format to preserve quality and reduce file size. Convert to JPEG only when the destination requires it (e.g., a server that rejects HEIC).
404
+ ## 9. Picker appearance
523
405
 
524
- ```swift
525
- import PhotosUI
526
-
527
- // Request the current representation to avoid transcoding
528
- var config = PHPickerConfiguration(photoLibrary: .shared())
529
- config.preferredAssetRepresentationMode = .current // Keeps HEIC as-is
530
- // vs .compatible which transcodes to JPEG
531
- ```
532
-
533
- ---
534
-
535
- ## 9. Custom PhotosPicker Appearance
536
-
537
- Customize the picker trigger with any SwiftUI view using the label closure:
406
+ Any SwiftUI view can be the picker's label:
538
407
 
539
408
  ```swift
540
- PhotosPicker(selection: $selectedItems, maxSelectionCount: 3, matching: .images) {
541
- Label("Add Photos", systemImage: "photo.on.rectangle.angled")
542
- .font(.headline)
409
+ PhotosPicker(selection: $picks, maxSelectionCount: 4, matching: .images) {
410
+ Label("Attach", systemImage: "photo.on.rectangle.angled")
543
411
  .padding()
544
- .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12))
412
+ .background(.ultraThinMaterial, in: .rect(cornerRadius: 12))
545
413
  }
546
414
  ```
547
415
 
548
- ### Inline PhotosPicker (iOS 17+)
549
-
550
- Display the picker inline rather than as a sheet:
416
+ iOS 17 adds presentation control:
551
417
 
552
418
  ```swift
553
- @available(iOS 17.0, *)
554
- PhotosPicker(
555
- selection: $selectedItems,
556
- maxSelectionCount: 5,
557
- matching: .images
558
- )
559
- .photosPickerStyle(.inline)
560
- .frame(height: 300)
419
+ PhotosPicker("Pick", selection: $picks, selectionBehavior: .continuous, matching: .images)
420
+ .photosPickerStyle(.inline)
421
+ .photosPickerAccessoryVisibility(.hidden, edges: .bottom)
422
+ .photosPickerDisabledCapabilities([.search, .collectionNavigation])
423
+ .frame(height: 300)
561
424
  ```
562
425
 
563
- ### Photos Picker Access Behavior (iOS 17+)
426
+ - `.photosPickerStyle(.inline)` embeds the picker in the layout instead of a
427
+ sheet; `.compact` gives a single row. Give an inline picker a fixed height.
428
+ - `.continuous` selection reports changes as the person taps, which suits an
429
+ embedded picker.
430
+ - Limited library access is not a picker modifier. With `.limited` status, let
431
+ the person revise the selection through
432
+ `PHPhotoLibrary.shared().presentLimitedLibraryPicker(from:)`, and set
433
+ `PHPhotoLibraryPreventAutomaticLimitedAccessAlert` in Info.plist if the app
434
+ offers its own entry point for that.
564
435
 
565
- Control how the picker interacts with limited library access:
436
+ ## 10. Cropping
566
437
 
567
- ```swift
568
- @available(iOS 17.0, *)
569
- PhotosPicker(selection: $selectedItems, matching: .images)
570
- .photosPickerAccessBehavior(.limited) // Only show user-approved photos
571
- // .automatic (default) -- system decides
572
- // .limited -- only previously approved photos
573
- ```
574
-
575
- ---
576
-
577
- ## 10. Image Cropping Pattern
578
-
579
- A basic square crop using Core Graphics after selecting a photo:
438
+ A centered square crop needs only Core Graphics:
580
439
 
581
440
  ```swift
582
- import UIKit
583
-
584
- func cropToSquare(_ image: UIImage) -> UIImage? {
585
- guard let cgImage = image.cgImage else { return nil }
586
-
587
- let side = min(cgImage.width, cgImage.height)
588
- let x = (cgImage.width - side) / 2
589
- let y = (cgImage.height - side) / 2
590
- let cropRect = CGRect(x: x, y: y, width: side, height: side)
591
-
592
- guard let cropped = cgImage.cropping(to: cropRect) else { return nil }
593
- return UIImage(cgImage: cropped, scale: image.scale, orientation: image.imageOrientation)
441
+ func centerSquare(of photo: UIImage) -> UIImage? {
442
+ guard let bitmap = photo.cgImage else { return nil }
443
+ let side = min(bitmap.width, bitmap.height)
444
+ let frame = CGRect(x: (bitmap.width - side) / 2, y: (bitmap.height - side) / 2, width: side, height: side)
445
+ guard let square = bitmap.cropping(to: frame) else { return nil }
446
+ return UIImage(cgImage: square, scale: photo.scale, orientation: photo.imageOrientation)
594
447
  }
595
448
  ```
596
449
 
597
- For interactive cropping, consider wrapping a third-party crop view or building a gesture-based crop overlay in SwiftUI. The system does not provide a built-in crop controller.
450
+ The system offers no crop controller. Interactive cropping means wrapping a
451
+ third-party view or building a drag-and-pinch overlay in SwiftUI yourself.