@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +266 -267
  5. package/package.json +2 -2
  6. package/pipeline/scripts/_notices.mjs +1 -1
  7. package/pipeline/skills/.skill-manifest.json +68 -68
  8. package/pipeline/skills/shared/README.md +70 -70
  9. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  10. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  11. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  12. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  13. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  14. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  15. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  16. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  17. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  18. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  19. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  20. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  21. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  22. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  23. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  24. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  25. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  26. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  27. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  28. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  29. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  30. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  31. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  32. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  34. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  35. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  36. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  37. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  38. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  39. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  40. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  41. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  42. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  43. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  44. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  45. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  46. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  47. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  48. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  49. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  50. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  51. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  52. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  53. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  54. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  55. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  56. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  57. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  58. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  59. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  60. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  61. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  62. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  63. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  64. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  65. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  66. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  67. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  68. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  69. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  70. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  71. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  72. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  73. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  74. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  75. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  76. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  77. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  78. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  79. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  80. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  81. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  82. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  83. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  84. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  85. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  86. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  87. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  88. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  89. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  90. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  91. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  92. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  93. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  94. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  95. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  96. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  97. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  98. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  99. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  100. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  101. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  102. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  103. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  104. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  105. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  106. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  107. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  108. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  109. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  111. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  112. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  113. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  114. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  115. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  116. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  117. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  118. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  119. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  120. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  121. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  122. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  123. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  124. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  125. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  126. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  127. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  128. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  129. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  130. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  131. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  132. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  133. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  134. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  135. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  136. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  137. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  138. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  139. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  140. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  141. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  142. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  143. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  144. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  145. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  146. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  147. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  148. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  149. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  150. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  151. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  152. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  153. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  154. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  155. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  156. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  157. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  158. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  159. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  160. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  161. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  162. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  163. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  164. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  165. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  166. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  167. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  168. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  169. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  170. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  171. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  172. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  173. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  174. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  175. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  176. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  177. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  178. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  179. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  180. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  181. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  182. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  183. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  184. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  185. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  186. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  187. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  188. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  189. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  190. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  191. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  192. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  193. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  194. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  195. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  196. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  197. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  198. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  199. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  200. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  201. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  202. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  203. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  204. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  205. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  206. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  207. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  208. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  209. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  210. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  211. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  212. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  213. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  214. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  215. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  216. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  217. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  218. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  219. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  220. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  221. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  222. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  223. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  224. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  225. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  226. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  227. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  228. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  229. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  230. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  231. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  232. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  233. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  234. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  235. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  236. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  237. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  238. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  239. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  240. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  241. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  242. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  243. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  244. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  245. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  246. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  247. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  248. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  249. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  250. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  251. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  252. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  253. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  254. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  255. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  256. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  257. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  258. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  259. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  260. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  261. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  262. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  263. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  264. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,1242 +1,935 @@
1
- # Deprecated API Migration Guide
1
+ # Deprecated API Migration
2
2
 
3
- A comprehensive mapping of deprecated-to-modern SwiftUI and iOS APIs from iOS 15 through iOS 26. Each section shows the old pattern, the modern replacement, and migration notes. Target iOS 26 with Swift 6.3; backward-compatible to iOS 16 unless noted.
3
+ A map from older SwiftUI and iOS patterns (iOS 15 era through iOS 26) to their
4
+ current replacements. Each entry shows the old form, the new form, and notes.
5
+ Code is written for iOS 26 and Swift 6.3. Every entry names the release its
6
+ replacement needs, so check that against the project's deployment target
7
+ before migrating; nothing here assumes a single baseline.
8
+
9
+ Several old APIs here (`NavigationView`, `foregroundColor`, `cornerRadius`,
10
+ `ActionSheet`, the `Alert` struct, `presentationMode`, `tabItem`, the
11
+ `.navigationBar` toolbar placements) carry `deprecated: 100000.0` in the SDK.
12
+ Apple's documentation lists them as deprecated, but the compiler does not warn,
13
+ so a clean build does not mean they are gone; search for them.
4
14
 
5
15
  ## Contents
6
- - NavigationView to NavigationStack
7
- - NavigationView Sidebar to NavigationSplitView
8
- - ObservableObject / `@Published` / `@StateObject` to `@Observable` / `@State`
9
- - `@ObservedObject` to let / `@Bindable`
10
- - `@EnvironmentObject` to `@Environment`
11
- - foregroundColor to foregroundStyle
12
- - .onChange single-value to two-value
13
- - ActionSheet to confirmationDialog
14
- - Alert (Legacy) to modern .alert
15
- - AnyView to `@ViewBuilder`
16
- - .onAppear + Task to .task
17
- - presentationMode to dismiss
18
- - GeometryReader to Layout / containerRelativeFrame
19
- - PreviewProvider to #Preview
20
- - XCTest to Swift Testing
21
- - EditButton/.onDelete to .swipeActions
22
- - UIApplication.shared.open to openURL
23
- - `@FetchRequest` to @Query (SwiftData)
24
- - some vs any return types
25
- - .sheet(item:) for sheet presentation
26
- - Color.resolve(in:) usage
27
- - ForEach with Identifiable
28
- - Toolbar placement updates
29
-
30
- ## NavigationView to NavigationStack
31
-
32
- NavigationView was deprecated in iOS 16. Use NavigationStack for push-based navigation with a single column, or NavigationSplitView for multi-column layouts.
33
-
34
- ### Before (Deprecated)
35
16
 
36
- ```swift
37
- struct ContentView: View {
38
- var body: some View {
39
- NavigationView {
40
- List(items) { item in
41
- NavigationLink(destination: DetailView(item: item)) {
42
- Text(item.title)
43
- }
44
- }
45
- .navigationTitle("Items")
17
+ 1. [NavigationView to NavigationStack](#1-navigationview-to-navigationstack)
18
+ 2. [NavigationView Sidebar to NavigationSplitView](#2-navigationview-sidebar-to-navigationsplitview)
19
+ 3. [ObservableObject and @StateObject to @Observable and @State](#3-observableobject-and-stateobject-to-observable-and-state)
20
+ 4. [@ObservedObject to let or @Bindable](#4-observedobject-to-let-or-bindable)
21
+ 5. [@EnvironmentObject to @Environment](#5-environmentobject-to-environment)
22
+ 6. [foregroundColor to foregroundStyle](#6-foregroundcolor-to-foregroundstyle)
23
+ 7. [Single-Value onChange to Two-Value onChange](#7-single-value-onchange-to-two-value-onchange)
24
+ 8. [ActionSheet to confirmationDialog](#8-actionsheet-to-confirmationdialog)
25
+ 9. [Legacy Alert to alert with Actions](#9-legacy-alert-to-alert-with-actions)
26
+ 10. [AnyView to @ViewBuilder and Concrete Types](#10-anyview-to-viewbuilder-and-concrete-types)
27
+ 11. [onAppear with Task to .task](#11-onappear-with-task-to-task)
28
+ 12. [presentationMode to dismiss](#12-presentationmode-to-dismiss)
29
+ 13. [GeometryReader Overuse to containerRelativeFrame and Layout](#13-geometryreader-overuse-to-containerrelativeframe-and-layout)
30
+ 14. [PreviewProvider to the #Preview Macro](#14-previewprovider-to-the-preview-macro)
31
+ 15. [XCTest to Swift Testing](#15-xctest-to-swift-testing)
32
+ 16. [onDelete and EditButton to swipeActions](#16-ondelete-and-editbutton-to-swipeactions)
33
+ 17. [UIApplication.shared.open to openURL](#17-uiapplicationsharedopen-to-openurl)
34
+ 18. [@FetchRequest to @Query](#18-fetchrequest-to-query)
35
+ 19. [Type-Erased Returns to some and any](#19-type-erased-returns-to-some-and-any)
36
+ 20. [sheet(isPresented:) with an Optional to sheet(item:)](#20-sheetispresented-with-an-optional-to-sheetitem)
37
+ 21. [UIColor Interop to Asset Colors and Color.resolve](#21-uicolor-interop-to-asset-colors-and-colorresolve)
38
+ 22. [ForEach over a Range to Identifiable Data](#22-foreach-over-a-range-to-identifiable-data)
39
+ 23. [Toolbar Placements navigationBar to topBar](#23-toolbar-placements-navigationbar-to-topbar)
40
+ 24. [cornerRadius to clipShape](#24-cornerradius-to-clipshape)
41
+ 25. [tabItem to Tab (iOS 18)](#25-tabitem-to-tab-ios-18)
42
+ 26. [showsIndicators to scrollIndicators](#26-showsindicators-to-scrollindicators)
43
+
44
+ ## 1. NavigationView to NavigationStack
45
+
46
+ `NavigationView` has been superseded since iOS 16 and is marked for deprecation
47
+ (no compiler warning). Use `NavigationStack` (iOS 16+)
48
+ for one column of pushed screens and `NavigationSplitView` for several columns.
49
+
50
+ Before:
51
+
52
+ ```swift
53
+ NavigationView {
54
+ List(courses) { course in
55
+ NavigationLink(destination: CourseDetail(course: course)) {
56
+ Text(course.title)
46
57
  }
47
- .navigationViewStyle(.stack)
48
58
  }
49
59
  }
60
+ .navigationViewStyle(.stack)
50
61
  ```
51
62
 
52
- ### After (Modern)
63
+ After:
53
64
 
54
65
  ```swift
55
- struct ContentView: View {
56
- @State private var path: [Item] = []
66
+ @State private var path: [Course] = []
57
67
 
58
- var body: some View {
59
- NavigationStack(path: $path) {
60
- List(items) { item in
61
- NavigationLink(value: item) {
62
- Text(item.title)
63
- }
64
- }
65
- .navigationTitle("Items")
66
- .navigationDestination(for: Item.self) { item in
67
- DetailView(item: item)
68
- }
69
- }
68
+ NavigationStack(path: $path) {
69
+ List(courses) { course in
70
+ NavigationLink(course.title, value: course)
70
71
  }
72
+ .navigationDestination(for: Course.self) { CourseDetail(course: $0) }
71
73
  }
72
74
  ```
73
75
 
74
- ### Migration Notes
75
-
76
- NavigationStack gives you programmatic control over the navigation path via a binding. Value-based NavigationLink separates the trigger from the destination, keeping list rows lightweight. The `.navigationViewStyle(.stack)` modifier is no longer needed.
77
-
78
- ---
76
+ - The path binding gives code full control over what is pushed.
77
+ - A value-based `NavigationLink` separates the trigger from the destination, so
78
+ rows stay cheap.
79
+ - `.navigationViewStyle(.stack)` is no longer needed.
79
80
 
80
- ## NavigationView Sidebar to NavigationSplitView
81
+ ## 2. NavigationView Sidebar to NavigationSplitView
81
82
 
82
- ### Before (Deprecated)
83
+ Before:
83
84
 
84
85
  ```swift
85
- struct SidebarApp: View {
86
- var body: some View {
87
- NavigationView {
88
- SidebarList()
89
- DetailPlaceholder()
90
- }
91
- .navigationViewStyle(.columns)
92
- }
86
+ NavigationView {
87
+ TeamSidebar()
88
+ Text("Choose a team")
93
89
  }
90
+ .navigationViewStyle(.columns)
94
91
  ```
95
92
 
96
- ### After (Modern)
93
+ After (iOS 16+):
97
94
 
98
95
  ```swift
99
- struct SidebarApp: View {
100
- @State private var selectedCategory: Category?
101
- @State private var selectedItem: Item?
96
+ @State private var team: Team?
97
+ @State private var player: Player?
102
98
 
103
- var body: some View {
104
- NavigationSplitView {
105
- List(categories, selection: $selectedCategory) { category in
106
- Label(category.name, systemImage: category.icon)
107
- }
108
- .navigationTitle("Categories")
109
- } content: {
110
- if let category = selectedCategory {
111
- List(category.items, selection: $selectedItem) { item in
112
- Text(item.title)
113
- }
114
- } else {
115
- ContentUnavailableView("Select a Category",
116
- systemImage: "sidebar.left")
117
- }
118
- } detail: {
119
- if let item = selectedItem {
120
- DetailView(item: item)
121
- } else {
122
- ContentUnavailableView("Select an Item",
123
- systemImage: "doc.text")
124
- }
125
- }
99
+ NavigationSplitView {
100
+ List(teams, selection: $team) { NavigationLink($0.name, value: $0) }
101
+ } content: {
102
+ if let team {
103
+ List(team.players, selection: $player) { NavigationLink($0.name, value: $0) }
104
+ } else {
105
+ ContentUnavailableView("No team selected", systemImage: "sidebar.left")
106
+ }
107
+ } detail: {
108
+ if let player {
109
+ PlayerCard(player: player)
110
+ } else {
111
+ ContentUnavailableView("No player selected", systemImage: "doc.text")
126
112
  }
127
113
  }
128
114
  ```
129
115
 
130
- ### Migration Notes
131
-
132
- NavigationSplitView explicitly models two-column and three-column layouts. Column visibility is controlled via `NavigationSplitViewVisibility` and `columnVisibility` bindings. On compact size classes the split view collapses into a NavigationStack automatically.
116
+ - The split view states its two- or three-column layout explicitly.
117
+ - Column visibility is a `NavigationSplitViewVisibility` value passed through
118
+ the `columnVisibility:` binding.
119
+ - In compact size classes it collapses into a stack on its own.
133
120
 
134
- ---
121
+ ## 3. ObservableObject and @StateObject to @Observable and @State
135
122
 
136
- ## ObservableObject / `@Published` / `@StateObject` to `@Observable` / `@State`
123
+ The Observation framework (iOS 17+) replaces observation built on Combine. An
124
+ `@Observable` class records which properties each view reads.
137
125
 
138
- The Observation framework (iOS 17+) replaces Combine-based observation. Classes annotated with `@Observable` track property access automatically -- no `@Published` wrappers needed.
139
-
140
- ### Before (Superseded)
126
+ Before:
141
127
 
142
128
  ```swift
143
- class UserSettings: ObservableObject {
144
- @Published var username: String = ""
145
- @Published var notificationsEnabled: Bool = true
146
- @Published var theme: Theme = .system
147
-
148
- func resetToDefaults() {
149
- username = ""
150
- notificationsEnabled = true
151
- theme = .system
152
- }
129
+ final class ReaderSettings: ObservableObject {
130
+ @Published var fontSize = 16.0
131
+ @Published var isNightMode = false
132
+ @Published var typeface = "Serif"
133
+
134
+ func restoreDefaults() { fontSize = 16; isNightMode = false; typeface = "Serif" }
153
135
  }
154
136
 
155
- struct SettingsView: View {
156
- @StateObject private var settings = UserSettings()
137
+ struct ReaderSettingsView: View {
138
+ @StateObject private var settings = ReaderSettings()
157
139
 
158
140
  var body: some View {
159
141
  Form {
160
- TextField("Username", text: $settings.username)
161
- Toggle("Notifications", isOn: $settings.notificationsEnabled)
162
- Picker("Theme", selection: $settings.theme) {
163
- ForEach(Theme.allCases) { theme in
164
- Text(theme.rawValue).tag(theme)
165
- }
142
+ Slider(value: $settings.fontSize, in: 12...32)
143
+ Toggle("Night mode", isOn: $settings.isNightMode)
144
+ Picker("Typeface", selection: $settings.typeface) {
145
+ Text("Serif").tag("Serif")
146
+ Text("Sans").tag("Sans")
166
147
  }
167
148
  }
168
149
  }
169
150
  }
170
151
  ```
171
152
 
172
- ### After (Modern)
153
+ After:
173
154
 
174
155
  ```swift
175
- @Observable
176
- class UserSettings {
177
- var username: String = ""
178
- var notificationsEnabled: Bool = true
179
- var theme: Theme = .system
180
-
181
- func resetToDefaults() {
182
- username = ""
183
- notificationsEnabled = true
184
- theme = .system
185
- }
156
+ @MainActor @Observable
157
+ final class ReaderSettings {
158
+ var fontSize = 16.0
159
+ var isNightMode = false
160
+ var typeface = "Serif"
161
+
162
+ func restoreDefaults() { fontSize = 16; isNightMode = false; typeface = "Serif" }
186
163
  }
187
164
 
188
- struct SettingsView: View {
189
- @State private var settings = UserSettings()
165
+ struct ReaderSettingsView: View {
166
+ @State private var settings = ReaderSettings()
190
167
 
191
168
  var body: some View {
192
169
  Form {
193
- TextField("Username", text: $settings.username)
194
- Toggle("Notifications", isOn: $settings.notificationsEnabled)
195
- Picker("Theme", selection: $settings.theme) {
196
- ForEach(Theme.allCases) { theme in
197
- Text(theme.rawValue).tag(theme)
198
- }
170
+ Slider(value: $settings.fontSize, in: 12...32)
171
+ Toggle("Night mode", isOn: $settings.isNightMode)
172
+ Picker("Typeface", selection: $settings.typeface) {
173
+ Text("Serif").tag("Serif")
174
+ Text("Sans").tag("Sans")
199
175
  }
200
176
  }
201
177
  }
202
178
  }
203
179
  ```
204
180
 
205
- ### Migration Notes
181
+ Steps:
206
182
 
207
- - Replace `ObservableObject` conformance with the `@Observable` macro.
208
- - Remove all `@Published` property wrappers -- observation is automatic.
209
- - Replace `@StateObject` with `@State` for owned instances.
210
- - Computed properties that depend on stored properties are tracked automatically.
211
- - The view only re-evaluates when properties it actually reads change, so fine-grained observation is free.
212
- - **Requires iOS 17+ minimum deployment target.** `ObservableObject` is not formally deprecated (no compiler warning) -- it is superseded. Do not rewrite working `ObservableObject` code if the project targets iOS 16 or earlier.
183
+ 1. Replace the `ObservableObject` conformance with the `@Observable` macro.
184
+ 2. Delete every `@Published`; tracking is automatic.
185
+ 3. Change owned instances from `@StateObject` to `@State`. The body stays as it was:
186
+ `$settings.fontSize` and the other bindings still work.
213
187
 
214
- ---
188
+ Notes:
215
189
 
216
- ## `@ObservedObject` to let / `@Bindable`
190
+ - Computed properties built on stored ones are tracked without extra work.
191
+ - A view re-evaluates only when a property it actually read changes, at no
192
+ extra cost.
193
+ - Needs an iOS 17 deployment target.
194
+ - `ObservableObject` is superseded, not formally deprecated, and the compiler
195
+ does not warn about it. On projects that target iOS 16 or earlier, leave
196
+ working `ObservableObject` code alone.
217
197
 
218
- ### Before (Superseded)
198
+ ## 4. @ObservedObject to let or @Bindable
199
+
200
+ Before:
219
201
 
220
202
  ```swift
221
- struct ProfileEditor: View {
222
- @ObservedObject var profile: ProfileModel
203
+ struct MemberCard: View {
204
+ @ObservedObject var member: Member
223
205
 
224
206
  var body: some View {
225
- TextField("Name", text: $profile.name)
226
- Toggle("Public", isOn: $profile.isPublic)
207
+ TextField("Nickname", text: $member.nickname)
208
+ Toggle("Visible", isOn: $member.isVisible)
227
209
  }
228
210
  }
229
211
  ```
230
212
 
231
- ### After (Modern)
232
-
233
- When you only need to read properties, use a plain `let`:
213
+ After, read only:
234
214
 
235
215
  ```swift
236
- struct ProfileDisplay: View {
237
- let profile: ProfileModel // @Observable class
238
-
239
- var body: some View {
240
- Text(profile.name)
241
- Text(profile.isPublic ? "Public" : "Private")
242
- }
216
+ struct MemberBadge: View {
217
+ let member: Member
218
+ var body: some View { Text(member.nickname) }
243
219
  }
244
220
  ```
245
221
 
246
- When you need to create bindings, use `@Bindable`:
222
+ After, with bindings:
247
223
 
248
224
  ```swift
249
- struct ProfileEditor: View {
250
- @Bindable var profile: ProfileModel
225
+ struct MemberCard: View {
226
+ @Bindable var member: Member
251
227
 
252
228
  var body: some View {
253
- TextField("Name", text: $profile.name)
254
- Toggle("Public", isOn: $profile.isPublic)
229
+ TextField("Nickname", text: $member.nickname)
230
+ Toggle("Visible", isOn: $member.isVisible)
255
231
  }
256
232
  }
257
233
  ```
258
234
 
259
- ### Migration Notes
260
-
261
- With `@Observable`, you no longer need `@ObservedObject` to subscribe to changes. A plain `let` constant already triggers view updates when read properties change. Use `@Bindable` only when you need two-way bindings via `$` syntax.
235
+ With `@Observable` types, `@ObservedObject` has no role. A plain `let` already
236
+ updates the view for every property it reads; `@Bindable` is only for `$`
237
+ bindings.
262
238
 
263
- ---
239
+ ## 5. @EnvironmentObject to @Environment
264
240
 
265
- ## `@EnvironmentObject` to `@Environment`
266
-
267
- ### Before (Superseded)
241
+ Before:
268
242
 
269
243
  ```swift
270
- // Injection
271
- ContentView()
272
- .environmentObject(authManager)
273
-
274
- // Usage
275
- struct ContentView: View {
276
- @EnvironmentObject var auth: AuthManager
244
+ RootView().environmentObject(session)
277
245
 
278
- var body: some View {
279
- if auth.isLoggedIn {
280
- HomeView()
281
- } else {
282
- LoginView()
283
- }
284
- }
246
+ struct GateView: View {
247
+ @EnvironmentObject var session: Session
248
+ var body: some View { session.isSignedIn ? AnyView(HomeView()) : AnyView(SignInView()) }
285
249
  }
286
250
  ```
287
251
 
288
- ### After (Modern)
252
+ After (iOS 17+):
289
253
 
290
254
  ```swift
291
- // Injection
292
- ContentView()
293
- .environment(authManager)
255
+ RootView().environment(session)
294
256
 
295
- // Usage
296
- struct ContentView: View {
297
- @Environment(AuthManager.self) private var auth
257
+ struct GateView: View {
258
+ @Environment(Session.self) private var session
298
259
 
299
260
  var body: some View {
300
- if auth.isLoggedIn {
301
- HomeView()
302
- } else {
303
- LoginView()
304
- }
261
+ if session.isSignedIn { HomeView() } else { SignInView() }
305
262
  }
306
263
  }
307
264
  ```
308
265
 
309
- ### Migration Notes
310
-
311
- With `@Observable`, use `.environment(_:)` (the type-keyed overload) instead of `.environmentObject(_:)`. Read with `@Environment(Type.self)`. If you need bindings from an environment-injected object, pull it into a local `@Bindable`:
266
+ Bindings from an environment object come from a local `@Bindable` copy:
312
267
 
313
268
  ```swift
314
- struct ContentView: View {
315
- @Environment(AuthManager.self) private var auth
316
-
317
- var body: some View {
318
- @Bindable var auth = auth
319
- Toggle("Remember Me", isOn: $auth.rememberMe)
320
- }
269
+ var body: some View {
270
+ @Bindable var session = session
271
+ Toggle("Stay signed in", isOn: $session.staySignedIn)
321
272
  }
322
273
  ```
323
274
 
324
- ---
325
-
326
- ## foregroundColor(_:) to foregroundStyle(_:)
327
-
328
- `foregroundColor(_:)` was deprecated in iOS 17. Its replacement, `foregroundStyle(_:)`, accepts any `ShapeStyle` -- not just `Color` -- enabling gradients, hierarchical styles, and materials directly.
329
-
330
- ### Before (Deprecated)
331
-
332
- ```swift
333
- Text("Hello")
334
- .foregroundColor(.red)
335
-
336
- Text("Secondary")
337
- .foregroundColor(.secondary)
338
- ```
275
+ ## 6. foregroundColor to foregroundStyle
339
276
 
340
- ### After (Modern)
277
+ `foregroundColor(_:)` is marked for deprecation and renamed to
278
+ `foregroundStyle(_:)`; the compiler does not warn yet. `foregroundStyle(_:)`
279
+ (iOS 15+) takes any `ShapeStyle`, so gradients, hierarchical styles and
280
+ materials work directly.
341
281
 
342
282
  ```swift
343
- Text("Hello")
344
- .foregroundStyle(.red)
345
-
346
- Text("Secondary")
347
- .foregroundStyle(.secondary)
348
-
349
- // Gradient -- not possible with foregroundColor
350
- Text("Gradient")
351
- .foregroundStyle(
352
- .linearGradient(colors: [.blue, .purple],
353
- startPoint: .leading, endPoint: .trailing)
354
- )
283
+ Text("Overdue").foregroundStyle(.red)
284
+ Text("Updated 2 min ago").foregroundStyle(.secondary)
285
+ Text("Premium")
286
+ .foregroundStyle(.linearGradient(colors: [.orange, .pink],
287
+ startPoint: .leading, endPoint: .trailing))
355
288
  ```
356
289
 
357
- ### Migration Notes
290
+ - Passing a `Color` is a drop-in replacement.
291
+ - Also accepts `.tint`, `.selection` and the hierarchical `.primary`,
292
+ `.secondary`, `.tertiary`, `.quaternary`. A gradient was never possible with
293
+ `foregroundColor`.
294
+ - `foregroundStyle(_:_:)` and `foregroundStyle(_:_:_:)` set two or three
295
+ hierarchy levels for child content in one call.
296
+ - `NSAttributedString.Key.foregroundColor` is unrelated: it is the Foundation
297
+ and UIKit attribute key used with `NSAttributedString`, Core Text and PDF
298
+ rendering. It is not deprecated and has no SwiftUI counterpart.
358
299
 
359
- `foregroundStyle(_:)` is a drop-in replacement when passing a `Color`. The broader `ShapeStyle` conformance also accepts gradients, `.tint`, `.selection`, and hierarchical styles (`.primary`, `.secondary`, `.tertiary`, `.quaternary`). Multi-level variants `foregroundStyle(_:_:)` and `foregroundStyle(_:_:_:)` set hierarchical styles for child content in one call.
300
+ ## 7. Single-Value onChange to Two-Value onChange
360
301
 
361
- **Not to be confused with** `NSAttributedString.Key.foregroundColor` -- that is a UIKit/Foundation attributed-string key used for Core Text, `NSAttributedString`, and PDF rendering. It is not deprecated and has no SwiftUI equivalent.
362
-
363
- ---
364
-
365
- ## .onChange(of:perform:) to Two-Value onChange
366
-
367
- The single-value `onChange` closure was deprecated in iOS 17. The new signature provides both the old and new values.
368
-
369
- ### Before (Deprecated)
302
+ The `onChange(of:perform:)` form whose closure takes one value is deprecated in
303
+ iOS 17.
370
304
 
371
305
  ```swift
372
- .onChange(of: searchText) { newValue in
373
- performSearch(newValue)
374
- }
375
- ```
376
-
377
- ### After (Modern)
306
+ // Before
307
+ .onChange(of: volume) { newVolume in player.setVolume(newVolume) }
378
308
 
379
- ```swift
380
- .onChange(of: searchText) { oldValue, newValue in
381
- performSearch(newValue)
309
+ // After (iOS 17+)
310
+ .onChange(of: volume) { oldVolume, newVolume in
311
+ if newVolume > oldVolume { showBoostHint = true }
312
+ player.setVolume(newVolume)
382
313
  }
314
+ .onChange(of: volume) { _, newVolume in player.setVolume(newVolume) }
315
+ .onChange(of: volume, initial: true) { _, newVolume in player.setVolume(newVolume) }
383
316
  ```
384
317
 
385
- If you only need the new value, use `_` for the old value:
386
-
387
- ```swift
388
- .onChange(of: searchText) { _, newValue in
389
- performSearch(newValue)
390
- }
391
- ```
318
+ - The closure receives the old and new values; write `_` for an old value you
319
+ do not use.
320
+ - Comparing old and new inline removes the need for a helper state property.
321
+ - `initial: true` also runs the closure when the view first appears.
392
322
 
393
- ### Migration Notes
323
+ ## 8. ActionSheet to confirmationDialog
394
324
 
395
- The two-value variant lets you compare old and new values inline without maintaining extra state. The `initial` parameter is also available if you need the callback to fire on first appearance:
325
+ Before:
396
326
 
397
327
  ```swift
398
- .onChange(of: searchText, initial: true) { _, newValue in
399
- performSearch(newValue)
328
+ .actionSheet(isPresented: $isAsking) {
329
+ ActionSheet(title: Text("Photo"), message: Text("Choose an action"), buttons: [
330
+ .default(Text("Save")) { save() },
331
+ .destructive(Text("Delete")) { delete() },
332
+ .cancel()
333
+ ])
400
334
  }
401
335
  ```
402
336
 
403
- ---
404
-
405
- ## ActionSheet to confirmationDialog
406
-
407
- ### Before (Deprecated)
337
+ After (iOS 15+):
408
338
 
409
339
  ```swift
410
- .actionSheet(isPresented: $showingOptions) {
411
- ActionSheet(
412
- title: Text("Choose an action"),
413
- message: Text("Select one of the options below"),
414
- buttons: [
415
- .default(Text("Share")) { shareItem() },
416
- .destructive(Text("Delete")) { deleteItem() },
417
- .cancel()
418
- ]
419
- )
420
- }
421
- ```
422
-
423
- ### After (Modern)
424
-
425
- ```swift
426
- .confirmationDialog("Choose an action",
427
- isPresented: $showingOptions,
428
- titleVisibility: .visible) {
429
- Button("Share") { shareItem() }
430
- Button("Delete", role: .destructive) { deleteItem() }
431
- Button("Cancel", role: .cancel) {}
340
+ .confirmationDialog("Photo", isPresented: $isAsking, titleVisibility: .visible) {
341
+ Button("Save", action: save)
342
+ Button("Delete", role: .destructive, action: delete)
343
+ Button("Cancel", role: .cancel) { }
432
344
  } message: {
433
- Text("Select one of the options below")
345
+ Text("Choose an action")
434
346
  }
435
347
  ```
436
348
 
437
- ### Migration Notes
349
+ - Regular `Button` views with roles replace the `ActionSheet.Button` array.
350
+ - `titleVisibility` controls the title; on iOS it is hidden by default.
351
+ - A cancel button is added for you when you leave it out.
438
352
 
439
- `.confirmationDialog` uses standard SwiftUI `Button` views with roles instead of an array of `ActionSheet.Button`. The `titleVisibility` parameter controls whether the title appears (it is hidden by default on iOS). A cancel-role button is added automatically if you omit one.
353
+ ## 9. Legacy Alert to alert with Actions
440
354
 
441
- ---
442
-
443
- ## Alert (Legacy) to Modern .alert with Actions
444
-
445
- ### Before (Deprecated)
355
+ Before:
446
356
 
447
357
  ```swift
448
- .alert(isPresented: $showingAlert) {
449
- Alert(
450
- title: Text("Delete Item?"),
451
- message: Text("This action cannot be undone."),
452
- primaryButton: .destructive(Text("Delete")) { deleteItem() },
453
- secondaryButton: .cancel()
454
- )
358
+ .alert(isPresented: $confirmLeave) {
359
+ Alert(title: Text("Leave group?"),
360
+ message: Text("You can rejoin later."),
361
+ primaryButton: .destructive(Text("Leave")) { leaveGroup() },
362
+ secondaryButton: .cancel())
455
363
  }
456
364
  ```
457
365
 
458
- ### After (Modern)
366
+ After (iOS 15+):
459
367
 
460
368
  ```swift
461
- .alert("Delete Item?", isPresented: $showingAlert) {
462
- Button("Delete", role: .destructive) { deleteItem() }
463
- Button("Cancel", role: .cancel) {}
369
+ .alert("Leave group?", isPresented: $confirmLeave) {
370
+ Button("Leave", role: .destructive, action: leaveGroup)
371
+ Button("Cancel", role: .cancel) { }
464
372
  } message: {
465
- Text("This action cannot be undone.")
373
+ Text("You can rejoin later.")
466
374
  }
467
375
  ```
468
376
 
469
- With a data item:
377
+ Passing the data being confirmed:
470
378
 
471
379
  ```swift
472
- .alert("Delete Item?", isPresented: $showingAlert, presenting: itemToDelete) { item in
473
- Button("Delete", role: .destructive) { delete(item) }
474
- } message: { item in
475
- Text("Delete \"\(item.title)\"? This cannot be undone.")
380
+ .alert("Remove device?", isPresented: $confirmRemoval, presenting: pendingDevice) { device in
381
+ Button("Remove \(device.name)", role: .destructive) { remove(device) }
382
+ } message: { device in
383
+ Text("\(device.name) will be signed out.")
476
384
  }
477
385
  ```
478
386
 
479
- ### Migration Notes
480
-
481
- The modern alert API accepts a `presenting` parameter to pass data directly into the alert closures, eliminating the need for separate optional state tracking.
387
+ With `presenting:`, both closures receive the item, so no separate optional
388
+ state has to be tracked.
482
389
 
483
- ---
390
+ ## 10. AnyView to @ViewBuilder and Concrete Types
484
391
 
485
- ## AnyView to `@ViewBuilder` and Concrete Types
486
-
487
- ### Before (Deprecated Pattern)
392
+ Before:
488
393
 
489
394
  ```swift
490
- func destination(for route: Route) -> AnyView {
491
- switch route {
492
- case .home: return AnyView(HomeView())
493
- case .profile: return AnyView(ProfileView())
494
- case .settings: return AnyView(SettingsView())
395
+ func screen(for step: CheckoutStep) -> AnyView {
396
+ switch step {
397
+ case .cart: AnyView(CartView())
398
+ case .address: AnyView(AddressView())
399
+ case .payment: AnyView(PaymentView())
495
400
  }
496
401
  }
497
402
  ```
498
403
 
499
- ### After (Modern)
404
+ After:
500
405
 
501
406
  ```swift
502
407
  @ViewBuilder
503
- func destination(for route: Route) -> some View {
504
- switch route {
505
- case .home: HomeView()
506
- case .profile: ProfileView()
507
- case .settings: SettingsView()
408
+ func screen(for step: CheckoutStep) -> some View {
409
+ switch step {
410
+ case .cart: CartView()
411
+ case .address: AddressView()
412
+ case .payment: PaymentView()
508
413
  }
509
414
  }
510
415
  ```
511
416
 
512
- ### Migration Notes
513
-
514
- `AnyView` erases type information, preventing SwiftUI from efficiently diffing and transitioning views. `@ViewBuilder` preserves concrete types, enabling the framework to optimize identity and transitions. Avoid `AnyView` unless interfacing with APIs that genuinely require heterogeneous view storage. In iOS 26, `AnyView` continues to work but remains a performance anti-pattern.
515
-
516
- ---
517
-
518
- ## .onAppear + Manual Task to .task
417
+ - `AnyView` hides the concrete type, which blocks efficient diffing and
418
+ transitions. `@ViewBuilder` keeps the types, so SwiftUI can preserve identity
419
+ and animate between branches.
420
+ - Keep `AnyView` for APIs that really must store views of different types in
421
+ one collection.
422
+ - On iOS 26 `AnyView` still works and is still a performance anti-pattern.
519
423
 
520
- ### Before (Deprecated Pattern)
424
+ ## 11. onAppear with Task to .task
521
425
 
522
426
  ```swift
523
- struct FeedView: View {
524
- @State private var posts: [Post] = []
525
-
526
- var body: some View {
527
- List(posts) { post in
528
- PostRow(post: post)
529
- }
530
- .onAppear {
531
- Task {
532
- posts = try await fetchPosts()
533
- }
534
- }
535
- }
427
+ // Before
428
+ .onAppear {
429
+ Task { receipts = try await ledger.receipts() }
536
430
  }
537
- ```
538
-
539
- ### After (Modern)
540
431
 
541
- ```swift
542
- struct FeedView: View {
543
- @State private var posts: [Post] = []
544
-
545
- var body: some View {
546
- List(posts) { post in
547
- PostRow(post: post)
548
- }
549
- .task {
550
- do {
551
- posts = try await fetchPosts()
552
- } catch {
553
- // handle error
554
- }
555
- }
556
- }
432
+ // After
433
+ .task {
434
+ do { receipts = try await ledger.receipts() }
435
+ catch { loadError = error }
557
436
  }
558
- ```
559
-
560
- ### Migration Notes
561
437
 
562
- `.task` automatically cancels the async work when the view disappears, preventing retain cycles and stale updates. Use `.task(id:)` to re-run the task when a dependency changes:
563
-
564
- ```swift
565
- .task(id: selectedCategory) {
566
- posts = try? await fetchPosts(for: selectedCategory)
438
+ // Re-run when a dependency changes
439
+ .task(id: selectedMonth) {
440
+ receipts = (try? await ledger.receipts(in: selectedMonth)) ?? []
567
441
  }
568
442
  ```
569
443
 
570
- ---
444
+ `.task` cancels its work when the view disappears, which avoids retain cycles
445
+ and updates to views that are gone. `.task(id:)` restarts when the id changes.
571
446
 
572
- ## `@Environment(\.presentationMode)` to `@Environment(\.dismiss)`
573
-
574
- ### Before (Deprecated)
447
+ ## 12. presentationMode to dismiss
575
448
 
576
449
  ```swift
577
- struct DetailView: View {
578
- @Environment(\.presentationMode) var presentationMode
450
+ // Before
451
+ @Environment(\.presentationMode) var presentationMode
452
+ Button("Close") { presentationMode.wrappedValue.dismiss() }
579
453
 
580
- var body: some View {
581
- Button("Done") {
582
- presentationMode.wrappedValue.dismiss()
583
- }
584
- }
585
- }
454
+ // After (iOS 15+)
455
+ @Environment(\.dismiss) private var dismiss
456
+ Button("Close") { dismiss() }
586
457
  ```
587
458
 
588
- ### After (Modern)
459
+ - `dismiss` is a `DismissAction` that you call directly; there is no
460
+ `wrappedValue`.
461
+ - It closes sheets, full-screen covers and pushed navigation destinations.
589
462
 
590
- ```swift
591
- struct DetailView: View {
592
- @Environment(\.dismiss) private var dismiss
463
+ ## 13. GeometryReader Overuse to containerRelativeFrame and Layout
593
464
 
594
- var body: some View {
595
- Button("Done") {
596
- dismiss()
597
- }
598
- }
599
- }
600
- ```
601
-
602
- ### Migration Notes
603
-
604
- `dismiss` is a callable `DismissAction`. Call it directly -- no `.wrappedValue` needed. Works for sheets, full-screen covers, and navigation push destinations.
605
-
606
- ---
465
+ `GeometryReader` has a performance cost and makes layout harder to compose.
466
+ The `Layout` protocol arrived in iOS 16 and `containerRelativeFrame` in iOS 17
467
+ for sizing relative to a container.
607
468
 
608
- ## GeometryReader Overuse to Layout Protocol and containerRelativeFrame
609
-
610
- GeometryReader has performance costs and complicates layout. iOS 16 introduced the Layout protocol, and iOS 17 added `containerRelativeFrame` for proportional sizing.
611
-
612
- ### Before (Deprecated Pattern)
469
+ Before:
613
470
 
614
471
  ```swift
615
472
  GeometryReader { proxy in
616
473
  HStack(spacing: 0) {
617
- SidePanel()
618
- .frame(width: proxy.size.width * 0.3)
619
- MainContent()
620
- .frame(width: proxy.size.width * 0.7)
474
+ MapPanel().frame(width: proxy.size.width * 0.65)
475
+ DetailsPanel().frame(width: proxy.size.width * 0.35)
621
476
  }
622
477
  }
623
478
  ```
624
479
 
625
- ### After (Modern) -- containerRelativeFrame (iOS 17+)
480
+ After, iOS 17+:
626
481
 
627
482
  ```swift
628
483
  HStack(spacing: 0) {
629
- SidePanel()
630
- .containerRelativeFrame(.horizontal) { length, _ in
631
- length * 0.3
632
- }
633
- MainContent()
634
- .containerRelativeFrame(.horizontal) { length, _ in
635
- length * 0.7
636
- }
484
+ MapPanel().containerRelativeFrame(.horizontal) { width, _ in width * 0.65 }
485
+ DetailsPanel().containerRelativeFrame(.horizontal) { width, _ in width * 0.35 }
637
486
  }
638
487
  ```
639
488
 
640
- ### After (Modern) -- Custom Layout (iOS 16+)
489
+ After, iOS 16+ with a custom layout:
641
490
 
642
491
  ```swift
643
- struct ProportionalHStack: Layout {
644
- var ratios: [CGFloat]
492
+ struct WeightedRow: Layout {
493
+ var weights: [CGFloat]
645
494
 
646
495
  func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
647
496
  proposal.replacingUnspecifiedDimensions()
648
497
  }
649
498
 
650
- func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
651
- guard subviews.count == ratios.count else { return }
499
+ func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize,
500
+ subviews: Subviews, cache: inout ()) {
501
+ guard weights.count == subviews.count else { return }
502
+ let total = weights.reduce(0, +)
652
503
  var x = bounds.minX
653
- for (index, subview) in subviews.enumerated() {
654
- let width = bounds.width * ratios[index]
504
+ for (subview, weight) in zip(subviews, weights) {
505
+ let width = bounds.width * weight / total
655
506
  subview.place(at: CGPoint(x: x, y: bounds.minY),
656
507
  proposal: ProposedViewSize(width: width, height: bounds.height))
657
508
  x += width
658
509
  }
659
510
  }
660
511
  }
661
-
662
- // Usage
663
- ProportionalHStack(ratios: [0.3, 0.7]) {
664
- SidePanel()
665
- MainContent()
666
- }
667
512
  ```
668
513
 
669
- ### Migration Notes
670
-
671
- GeometryReader is still appropriate when you genuinely need to read the proposed size and cannot express the layout declaratively. For proportional sizing, prefer `containerRelativeFrame`. For custom arrangements, prefer the Layout protocol. Both avoid the bottom-up sizing behavior that makes GeometryReader tricky to compose.
514
+ - `GeometryReader` is still right when you truly must read the proposed size
515
+ and cannot state the layout declaratively.
516
+ - Proportional sizing: `containerRelativeFrame`. Custom arrangements: the
517
+ `Layout` protocol. Both avoid the bottom-up sizing of `GeometryReader` that
518
+ makes composition difficult.
672
519
 
673
- ---
520
+ ## 14. PreviewProvider to the #Preview Macro
674
521
 
675
- ## UIHostingController Previews to #Preview Macro
676
-
677
- ### Before (Deprecated)
522
+ Before:
678
523
 
679
524
  ```swift
680
- struct ContentView_Previews: PreviewProvider {
525
+ struct BoardingPassView_Previews: PreviewProvider {
681
526
  static var previews: some View {
682
- ContentView()
683
- .previewDevice("iPhone 15 Pro")
684
-
685
- ContentView()
527
+ BoardingPassView(pass: .sample)
528
+ .previewDevice("iPhone 16 Pro")
686
529
  .preferredColorScheme(.dark)
687
530
  }
688
531
  }
689
532
  ```
690
533
 
691
- ### After (Modern)
534
+ After:
692
535
 
693
536
  ```swift
694
- #Preview("Light Mode") {
695
- ContentView()
537
+ #Preview("Light") {
538
+ BoardingPassView(pass: .sample)
696
539
  }
697
540
 
698
- #Preview("Dark Mode") {
699
- ContentView()
541
+ #Preview("Dark") {
542
+ BoardingPassView(pass: .sample)
700
543
  .preferredColorScheme(.dark)
701
544
  }
702
545
  ```
703
546
 
704
- Widget and UIKit previews:
547
+ Widgets and UIKit:
705
548
 
706
549
  ```swift
707
- #Preview("Timeline Entry", as: .systemSmall) {
708
- MyWidget()
550
+ #Preview("Small", as: .systemSmall) {
551
+ StepsWidget()
709
552
  } timeline: {
710
- SimpleEntry(date: .now)
553
+ StepsEntry(date: .now, steps: 4_200)
711
554
  }
712
555
 
713
- #Preview("UIKit Controller") {
714
- let vc = MyViewController()
715
- vc.title = "Preview"
716
- return vc
556
+ #Preview {
557
+ let controller = ScannerViewController()
558
+ controller.title = "Scanner"
559
+ return controller
717
560
  }
718
561
  ```
719
562
 
720
- ### Migration Notes
721
-
722
- The `#Preview` macro (iOS 17+) is less boilerplate and supports naming each preview directly. It works with SwiftUI views, UIKit view controllers, and WidgetKit timelines. Delete the entire `PreviewProvider` struct and replace with `#Preview` blocks.
723
-
724
- ---
563
+ - `#Preview` needs Xcode 15 and less boilerplate, and names each preview
564
+ directly. SwiftUI view previews work with any deployment target; the UIKit
565
+ and WidgetKit forms need iOS 17.
566
+ - Migration: delete the whole `PreviewProvider` struct and write `#Preview`
567
+ blocks in its place.
725
568
 
726
- ## XCTest to Swift Testing
569
+ ## 15. XCTest to Swift Testing
727
570
 
728
- Swift Testing (Xcode 16+) provides a modern, expressive test framework that coexists with XCTest.
571
+ Swift Testing needs Xcode 16 and runs alongside XCTest.
729
572
 
730
- ### Before (XCTest)
573
+ Before:
731
574
 
732
575
  ```swift
733
- import XCTest
734
- @testable import MyApp
576
+ final class FareCalculatorTests: XCTestCase {
577
+ var calculator: FareCalculator!
735
578
 
736
- final class CartTests: XCTestCase {
737
- var cart: Cart!
738
-
739
- override func setUp() {
740
- cart = Cart()
741
- }
742
-
743
- override func tearDown() {
744
- cart = nil
745
- }
579
+ override func setUp() { calculator = FareCalculator(currency: "EUR") }
580
+ override func tearDown() { calculator = nil }
746
581
 
747
- func testAddItem() throws {
748
- cart.add(Item(name: "Widget", price: 9.99))
749
- XCTAssertEqual(cart.items.count, 1)
750
- XCTAssertEqual(cart.total, 9.99, accuracy: 0.01)
582
+ func testBaseFare() {
583
+ XCTAssertEqual(calculator.fare(km: 10), 12.5, accuracy: 0.001)
751
584
  }
752
585
 
753
- func testEmptyCartTotal() {
754
- XCTAssertEqual(cart.total, 0)
755
- }
756
-
757
- func testDiscountCodes() throws {
758
- let codes = ["SAVE10", "SAVE20", "SAVE50"]
759
- for code in codes {
760
- cart.applyDiscount(code: code)
761
- XCTAssertTrue(cart.hasDiscount)
586
+ func testFaresArePositive() {
587
+ for km in [1, 5, 50] {
588
+ XCTAssertTrue(calculator.fare(km: km) > 0)
762
589
  }
763
590
  }
764
591
  }
765
592
  ```
766
593
 
767
- ### After (Swift Testing)
594
+ After:
768
595
 
769
596
  ```swift
770
597
  import Testing
771
- @testable import MyApp
772
598
 
773
- @Suite("Cart Tests")
774
- struct CartTests {
775
- let cart = Cart()
776
-
777
- @Test("Adding an item updates count and total")
778
- func addItem() {
779
- cart.add(Item(name: "Widget", price: 9.99))
780
- #expect(cart.items.count == 1)
781
- #expect(cart.total.isApproximatelyEqual(to: 9.99))
782
- }
599
+ @Suite("Fare calculator")
600
+ struct FareCalculatorTests {
601
+ let calculator = FareCalculator(currency: "EUR")
783
602
 
784
- @Test("Empty cart has zero total")
785
- func emptyCartTotal() {
786
- #expect(cart.total == 0)
603
+ @Test("Base fare for ten kilometres")
604
+ func baseFare() {
605
+ #expect(abs(calculator.fare(km: 10) - 12.5) < 0.001)
787
606
  }
788
607
 
789
- @Test("Discount codes", arguments: ["SAVE10", "SAVE20", "SAVE50"])
790
- func discountCodes(code: String) {
791
- cart.applyDiscount(code: code)
792
- #expect(cart.hasDiscount)
608
+ @Test("Fares are positive", arguments: [1, 5, 50])
609
+ func positive(km: Int) {
610
+ #expect(calculator.fare(km: km) > 0)
793
611
  }
794
612
  }
795
613
  ```
796
614
 
797
- ### Migration Notes
615
+ Steps:
798
616
 
799
- - Replace `XCTestCase` subclass with a plain struct annotated with `@Suite`.
800
- - Replace `setUp` / `tearDown` with an initializer and deinit (or just inline setup).
801
- - Replace `XCTAssert*` macros with `#expect(...)` and `#require(...)`.
802
- - Use `@Test("description", arguments:)` for parameterized tests instead of manual loops.
803
- - Swift Testing and XCTest targets can coexist in the same project during migration.
804
- - Use `@Test(.disabled("reason"))` instead of `XCTSkip`.
617
+ 1. Replace the `XCTestCase` subclass with a struct marked `@Suite`.
618
+ 2. Move `setUp` work into `init` or into stored property defaults. For
619
+ teardown, make the suite a `final class` or an `actor` and use `deinit`; a
620
+ plain struct cannot have one.
621
+ 3. Replace `XCTAssert*` calls with `#expect(...)`, and use `#require(...)` where
622
+ the test cannot continue without a value.
623
+ 4. Replace loops with `@Test("description", arguments:)`.
805
624
 
806
- ---
625
+ Notes:
807
626
 
808
- ## List EditButton to .swipeActions, .onMove, .onDelete
627
+ - XCTest and Swift Testing targets can live in the same project during the
628
+ move.
629
+ - `@Test(.disabled("reason"))` takes the place of skipping with `XCTSkip`;
630
+ `.enabled(if:)` covers conditional skips.
809
631
 
810
- ### Before (Deprecated Pattern)
632
+ ## 16. onDelete and EditButton to swipeActions
811
633
 
812
- ```swift
813
- struct ItemList: View {
814
- @State private var items = ["A", "B", "C"]
634
+ Before:
815
635
 
816
- var body: some View {
817
- NavigationView {
818
- List {
819
- ForEach(items, id: \.self) { item in
820
- Text(item)
821
- }
822
- .onDelete { items.remove(atOffsets: $0) }
823
- .onMove { items.move(fromOffsets: $0, toOffset: $1) }
824
- }
825
- .navigationTitle("Items")
826
- .toolbar { EditButton() }
827
- }
636
+ ```swift
637
+ NavigationView {
638
+ List {
639
+ ForEach(alarms) { AlarmRow(alarm: $0) }
640
+ .onDelete { alarms.remove(atOffsets: $0) }
641
+ .onMove { alarms.move(fromOffsets: $0, toOffset: $1) }
828
642
  }
643
+ .toolbar { EditButton() }
829
644
  }
830
645
  ```
831
646
 
832
- ### After (Modern)
647
+ After:
833
648
 
834
649
  ```swift
835
- struct ItemList: View {
836
- @State private var items = ["A", "B", "C"]
837
-
838
- var body: some View {
839
- NavigationStack {
840
- List {
841
- ForEach(items, id: \.self) { item in
842
- Text(item)
843
- .swipeActions(edge: .trailing, allowsFullSwipe: true) {
844
- Button("Delete", role: .destructive) {
845
- if let index = items.firstIndex(of: item) {
846
- items.remove(at: index)
847
- }
848
- }
849
- }
850
- .swipeActions(edge: .leading) {
851
- Button("Pin", systemImage: "pin") {
852
- pinItem(item)
853
- }
854
- .tint(.orange)
855
- }
650
+ NavigationStack {
651
+ List {
652
+ ForEach(alarms) { alarm in
653
+ AlarmRow(alarm: alarm)
654
+ .swipeActions(edge: .trailing, allowsFullSwipe: true) {
655
+ Button("Delete", role: .destructive) { remove(alarm) }
656
+ }
657
+ .swipeActions(edge: .leading) {
658
+ Button("Snooze") { snooze(alarm) }.tint(.orange)
856
659
  }
857
- .onMove { items.move(fromOffsets: $0, toOffset: $1) }
858
- }
859
- .navigationTitle("Items")
860
- .toolbar { EditButton() }
861
660
  }
661
+ .onMove { alarms.move(fromOffsets: $0, toOffset: $1) }
862
662
  }
663
+ .toolbar { EditButton() }
863
664
  }
864
665
  ```
865
666
 
866
- ### Migration Notes
867
-
868
- `.swipeActions` (iOS 15+) gives you per-row, multi-action swipe menus with custom tints and roles. `EditButton` and `.onMove` still work fine for reorder mode. The main migration is replacing `.onDelete` with a `.swipeActions` destructive button for richer swipe UX.
667
+ - `.swipeActions` (iOS 15+) supports several actions per row, each with its own
668
+ tint and role.
669
+ - `EditButton` and `.onMove` are still fine for reordering. The main change is
670
+ turning `.onDelete` into a destructive swipe action.
869
671
 
870
- ---
871
-
872
- ## UIApplication.shared.open to `@Environment(\.openURL)`
873
-
874
- ### Before (Deprecated Pattern)
672
+ ## 17. UIApplication.shared.open to openURL
875
673
 
876
674
  ```swift
877
- Button("Open Website") {
878
- if let url = URL(string: "https://example.com") {
879
- UIApplication.shared.open(url)
880
- }
881
- }
882
- ```
883
-
884
- ### After (Modern)
675
+ // Before
676
+ Button("Terms") { UIApplication.shared.open(termsURL) }
885
677
 
886
- ```swift
887
- struct LinkButton: View {
888
- @Environment(\.openURL) private var openURL
678
+ // After
679
+ @Environment(\.openURL) private var openURL
680
+ Button("Terms") { openURL(termsURL) }
889
681
 
890
- var body: some View {
891
- Button("Open Website") {
892
- openURL(URL(string: "https://example.com")!)
893
- }
894
- }
895
- }
896
- ```
897
-
898
- With a completion handler:
899
-
900
- ```swift
901
- openURL(url) { accepted in
902
- if !accepted {
903
- // handle failure to open URL
682
+ // Handling failure
683
+ Button("Terms") {
684
+ openURL(termsURL) { opened in
685
+ if !opened { showLinkError = true }
904
686
  }
905
687
  }
906
688
  ```
907
689
 
908
- ### Migration Notes
690
+ - `openURL` works on every Apple platform, and it can be replaced in the
691
+ environment to intercept or test link handling.
692
+ - Keep `UIApplication.shared` out of SwiftUI views.
909
693
 
910
- `@Environment(\.openURL)` works on all Apple platforms, not just iOS. It can be overridden in the environment for testing or to intercept URL opens. Avoid reaching for `UIApplication.shared` in SwiftUI views.
694
+ ## 18. @FetchRequest to @Query
911
695
 
912
- ---
696
+ Once the models move from Core Data to SwiftData, the `@Query` macro (iOS 17+)
697
+ supersedes `@FetchRequest`.
913
698
 
914
- ## `@FetchRequest` to #Query (SwiftData)
915
-
916
- Core Data's `@FetchRequest` is superseded by SwiftData's `@Query` macro when you migrate to SwiftData models.
917
-
918
- ### Before (Core Data)
699
+ Before:
919
700
 
920
701
  ```swift
921
- struct ItemListView: View {
922
- @FetchRequest(
923
- sortDescriptors: [NSSortDescriptor(keyPath: \CDItem.timestamp, ascending: false)],
924
- predicate: NSPredicate(format: "isCompleted == NO")
925
- ) private var items: FetchedResults<CDItem>
926
-
927
- var body: some View {
928
- List(items) { item in
929
- Text(item.title ?? "")
930
- }
931
- }
932
- }
702
+ @FetchRequest(
703
+ sortDescriptors: [NSSortDescriptor(keyPath: \CDExpense.date, ascending: false)],
704
+ predicate: NSPredicate(format: "amount > %f", 100.0)
705
+ )
706
+ private var expenses: FetchedResults<CDExpense>
933
707
  ```
934
708
 
935
- ### After (SwiftData)
709
+ After:
936
710
 
937
711
  ```swift
938
- struct ItemListView: View {
939
- @Query(
940
- filter: #Predicate<Item> { !$0.isCompleted },
941
- sort: \.timestamp,
942
- order: .reverse
943
- ) private var items: [Item]
944
-
945
- var body: some View {
946
- List(items) { item in
947
- Text(item.title)
948
- }
949
- }
950
- }
712
+ @Query(filter: #Predicate<Expense> { $0.amount > 100 },
713
+ sort: \.date, order: .reverse)
714
+ private var expenses: [Expense]
951
715
  ```
952
716
 
953
- ### Migration Notes
954
-
955
- `@Query` uses type-safe `#Predicate` instead of string-based `NSPredicate`. Sort descriptors use key paths directly. The model container is injected via `.modelContainer(for:)` on an ancestor view. SwiftData models are plain Swift classes with the `@Model` macro rather than NSManagedObject subclasses.
956
-
957
- ---
717
+ - `#Predicate` is checked by the compiler, unlike a format string, and sorting
718
+ takes key paths directly.
719
+ - An ancestor view supplies the container with `.modelContainer(for:)`.
720
+ - SwiftData models are ordinary Swift classes marked `@Model`, not
721
+ `NSManagedObject` subclasses.
958
722
 
959
- ## Opaque return types to some/any clarifications (Swift 5.7+)
723
+ ## 19. Type-Erased Returns to some and any
960
724
 
961
- ### Before
725
+ Before:
962
726
 
963
727
  ```swift
964
- func makeView() -> AnyView {
965
- AnyView(Text("Hello"))
966
- }
728
+ func makeHeader() -> AnyView { AnyView(Text("Inbox")) }
967
729
 
968
- protocol DataSource {
969
- func fetch() -> AnyPublisher<[Item], Error>
730
+ protocol MessageSource {
731
+ func messages() -> AnyPublisher<[Message], Error>
970
732
  }
971
733
  ```
972
734
 
973
- ### After (Modern)
735
+ After:
974
736
 
975
737
  ```swift
976
- func makeView() -> some View {
977
- Text("Hello")
978
- }
738
+ func makeHeader() -> some View { Text("Inbox") }
979
739
 
980
- protocol DataSource {
981
- func fetch() async throws -> [Item]
740
+ protocol MessageSource: Sendable {
741
+ func messages() async throws -> [Message]
982
742
  }
983
743
 
984
- // When you need a protocol-typed variable:
985
- let source: any DataSource = RemoteDataSource()
986
- ```
987
-
988
- ### Migration Notes
989
-
990
- Use `some` for opaque return types when the concrete type is fixed. Use `any` for existentials when you need to store heterogeneous conformances. Prefer `async throws` over Combine publishers for new code. Swift 5.7+ allows `some` in parameter position too:
991
-
992
- ```swift
993
- func display(_ view: some View) { ... }
744
+ let source: any MessageSource = RemoteMessageSource()
994
745
  ```
995
746
 
996
- ---
747
+ - `some` marks an opaque result whose concrete type is fixed. `any` marks an
748
+ existential that can hold different conforming types.
749
+ - In new code, prefer `async throws` to Combine publishers.
750
+ - Since Swift 5.7, `some` works in parameter position:
751
+ `func present(_ content: some View)`.
997
752
 
998
- ## .sheet(item:) with Optional Identifiable to Modern Pattern
753
+ ## 20. sheet(isPresented:) with an Optional to sheet(item:)
999
754
 
1000
- ### Before (Fragile Pattern)
755
+ Before, two pieces of state that can disagree:
1001
756
 
1002
757
  ```swift
1003
- @State private var selectedItem: Item?
1004
- @State private var showingSheet = false
1005
-
1006
- Button {
1007
- selectedItem = item
1008
- showingSheet = true
1009
- } label: {
1010
- ItemRow(item: item)
1011
- }
1012
- .buttonStyle(.plain)
1013
- .sheet(isPresented: $showingSheet) {
1014
- if let item = selectedItem {
1015
- DetailView(item: item)
1016
- }
758
+ @State private var editingNote: Note?
759
+ @State private var isEditing = false
760
+
761
+ .sheet(isPresented: $isEditing) {
762
+ if let editingNote { NoteEditor(note: editingNote) }
1017
763
  }
1018
764
  ```
1019
765
 
1020
- ### After (Modern)
766
+ After:
1021
767
 
1022
768
  ```swift
1023
- @State private var selectedItem: Item?
769
+ @State private var editingNote: Note?
1024
770
 
1025
- Button {
1026
- selectedItem = item
1027
- } label: {
1028
- ItemRow(item: item)
1029
- }
1030
- .buttonStyle(.plain)
1031
- .sheet(item: $selectedItem) { item in
1032
- DetailView(item: item)
771
+ .sheet(item: $editingNote) { note in
772
+ NoteEditor(note: note)
1033
773
  }
1034
774
  ```
1035
775
 
1036
- ### Migration Notes
776
+ - `.sheet(item:)` removes the chance of the flag and the value falling out of
777
+ step.
778
+ - The sheet appears when the binding becomes non-nil and closes when it
779
+ returns to nil; the closure receives the unwrapped value.
1037
780
 
1038
- Using `.sheet(item:)` eliminates the dual-state problem where `showingSheet` and `selectedItem` can become out of sync. The sheet presents when the binding becomes non-nil and dismisses when it becomes nil. The unwrapped value is passed directly into the closure.
781
+ ## 21. UIColor Interop to Asset Colors and Color.resolve
1039
782
 
1040
- ---
783
+ Building a `Color` from components, directly or through
784
+ `Color(uiColor: UIColor(red:green:blue:alpha:))`, is still fine. Better
785
+ options exist for the two common needs:
1041
786
 
1042
- ## UIColor / Color(UIColor:) to Color ShaderLibrary (iOS 17+)
1043
-
1044
- ### Before
787
+ - Fixed brand colors: an asset catalog color such as `Color("Ocean")` is always
788
+ the first choice; it carries light, dark and high-contrast variants.
789
+ - Reading actual components at runtime: `Color.resolve(in:)` (iOS 17+) returns
790
+ a `Color.Resolved` with `red`, `green`, `blue` and `opacity` for the current
791
+ environment. That covers most of the reasons code used to drop into
792
+ `UIColor`.
1045
793
 
1046
794
  ```swift
1047
- let color = UIColor(red: 0.2, green: 0.5, blue: 0.8, alpha: 1.0)
1048
- let swiftUIColor = Color(uiColor: color)
1049
- ```
1050
-
1051
- ### After (Modern)
1052
-
1053
- Color initialization from components is still fine, but for dynamic colors prefer:
795
+ struct ContrastBadge: View {
796
+ @Environment(\.self) private var environment
797
+ let tint: Color
1054
798
 
1055
- ```swift
1056
- // Custom colors via asset catalogs (always preferred)
1057
- let brand = Color("BrandBlue")
1058
-
1059
- // Resolved colors for interop (iOS 17+)
1060
- @Environment(\.self) var environment
1061
-
1062
- let resolved = Color.blue.resolve(in: environment)
1063
- // resolved.red, resolved.green, resolved.blue, resolved.opacity
799
+ var body: some View {
800
+ let resolved = tint.resolve(in: environment)
801
+ let lightBackground = (resolved.red + resolved.green + resolved.blue) / 3 > 0.6
802
+ Text("New")
803
+ .padding(6)
804
+ .background(tint)
805
+ .foregroundStyle(lightBackground ? .black : .white)
806
+ }
807
+ }
1064
808
  ```
1065
809
 
1066
- ### Migration Notes
1067
-
1068
- `Color.resolve(in:)` (iOS 17+) gives you concrete RGBA values in the current environment, replacing many UIColor interop needs. For custom runtime color manipulations, use resolved colors. For static brand colors, use asset catalogs.
810
+ Use resolved colors for runtime manipulation and asset catalogs for static
811
+ brand colors.
1069
812
 
1070
- ---
813
+ ## 22. ForEach over a Range to Identifiable Data
1071
814
 
1072
- ## ForEach with Range to ForEach with Identifiable / indices
1073
-
1074
- ### Before (Fragile Pattern)
815
+ Before:
1075
816
 
1076
817
  ```swift
1077
- ForEach(0..<items.count) { index in
1078
- Text(items[index].name)
818
+ ForEach(0..<songs.count) { index in
819
+ Text(songs[index].title)
1079
820
  }
1080
821
  ```
1081
822
 
1082
- ### After (Modern)
823
+ After:
1083
824
 
1084
825
  ```swift
1085
- // Identifiable models
1086
- ForEach(items) { item in
1087
- Text(item.name)
826
+ ForEach(songs) { song in
827
+ Text(song.title)
1088
828
  }
1089
829
 
1090
- // When you need the index
1091
- ForEach(Array(items.enumerated()), id: \.element.id) { index, item in
1092
- Text("\(index + 1). \(item.name)")
830
+ ForEach(Array(songs.enumerated()), id: \.element.id) { position, song in
831
+ Text("\(position + 1). \(song.title)")
1093
832
  }
1094
833
 
1095
- // Subranges with bindable access
1096
- ForEach($items) { $item in
1097
- TextField("Name", text: $item.name)
834
+ ForEach($songs) { $song in
835
+ TextField("Title", text: $song.title)
1098
836
  }
1099
837
  ```
1100
838
 
1101
- ### Migration Notes
1102
-
1103
- Constant-range `ForEach(0..<n)` is only safe when the range never changes. For dynamic data, always use identifiable collections. `ForEach($items)` provides direct bindings to each element without index arithmetic.
1104
-
1105
- ---
1106
-
1107
- ## .toolbar placement consolidation (iOS 26)
839
+ - `ForEach(0..<n)` is safe only when the range is constant. Data that can
840
+ change needs an identifiable collection. The Before code draws a compiler
841
+ warning: "non-constant range: argument must be an integer literal".
842
+ - `ForEach($items)` hands out a binding per element without index arithmetic.
1108
843
 
1109
- ### Before
844
+ ## 23. Toolbar Placements navigationBar to topBar
1110
845
 
1111
846
  ```swift
1112
- .toolbar {
1113
- ToolbarItem(placement: .navigationBarLeading) {
1114
- Button("Back") { dismiss() }
1115
- }
1116
- ToolbarItem(placement: .navigationBarTrailing) {
1117
- Button("Edit") { isEditing.toggle() }
1118
- }
1119
- ToolbarItem(placement: .bottomBar) {
1120
- Button("Add") { addItem() }
1121
- }
1122
- }
1123
- ```
1124
-
1125
- ### After (Modern)
847
+ // Before
848
+ ToolbarItem(placement: .navigationBarLeading) { closeButton }
849
+ ToolbarItem(placement: .navigationBarTrailing) { saveButton }
1126
850
 
1127
- ```swift
1128
- .toolbar {
1129
- ToolbarItem(placement: .topBarLeading) {
1130
- Button("Back") { dismiss() }
1131
- }
1132
- ToolbarItem(placement: .topBarTrailing) {
1133
- Button("Edit") { isEditing.toggle() }
1134
- }
1135
- ToolbarItem(placement: .bottomBar) {
1136
- Button("Add") { addItem() }
1137
- }
1138
- }
851
+ // After
852
+ ToolbarItem(placement: .topBarLeading) { closeButton }
853
+ ToolbarItem(placement: .topBarTrailing) { saveButton }
854
+ ToolbarItem(placement: .bottomBar) { statusText }
1139
855
  ```
1140
856
 
1141
- ### Migration Notes
857
+ - `.topBarLeading` and `.topBarTrailing` arrived with the iOS 17 SDK and are
858
+ back-deployed to iOS 14, so they work wherever the old names did. The
859
+ `.navigationBar` names are marked for deprecation in their favour. `.bottomBar` is
860
+ unchanged.
861
+ - The new names behave the same in `NavigationStack` and `NavigationSplitView`.
862
+ Prefer them for consistency across platforms.
1142
863
 
1143
- `.navigationBarLeading` and `.navigationBarTrailing` were renamed to `.topBarLeading` and `.topBarTrailing` (iOS 16+). The new names work consistently across NavigationStack and NavigationSplitView contexts. Prefer the new names for cross-platform consistency.
864
+ ## 24. cornerRadius to clipShape
1144
865
 
1145
- ---
1146
-
1147
- ## cornerRadius to clipShape(.rect(cornerRadius:))
1148
-
1149
- `.cornerRadius(_:)` was deprecated in iOS 17.
1150
-
1151
- ### Before (Deprecated)
1152
-
1153
- ```swift
1154
- RoundedRectangle(cornerRadius: 12)
1155
- .cornerRadius(12)
1156
-
1157
- Image("photo")
1158
- .cornerRadius(8)
1159
- ```
1160
-
1161
- ### After (Modern)
866
+ `.cornerRadius(_:)` is marked for deprecation (no compiler warning yet); Apple
867
+ points to `clipShape` instead.
1162
868
 
1163
869
  ```swift
1164
- RoundedRectangle(cornerRadius: 12)
870
+ Rectangle()
871
+ .fill(.blue)
872
+ .frame(width: 80, height: 80)
1165
873
  .clipShape(.rect(cornerRadius: 12))
1166
874
 
1167
- Image("photo")
1168
- .clipShape(.rect(cornerRadius: 8))
1169
- ```
1170
-
1171
- ### Migration Notes
875
+ Image("cover")
876
+ .resizable()
877
+ .scaledToFill()
878
+ .clipShape(.rect(cornerRadius: 20))
1172
879
 
1173
- `clipShape(.rect(cornerRadius:))` uses `RoundedRectangle` under the hood and also supports `cornerRadii` for per-corner control (iOS 16+):
1174
-
1175
- ```swift
1176
- .clipShape(.rect(cornerRadii: .init(topLeading: 12, bottomTrailing: 12)))
880
+ Color.orange
881
+ .clipShape(.rect(cornerRadii: .init(topLeading: 16, bottomTrailing: 16)))
1177
882
  ```
1178
883
 
1179
- ---
884
+ - `.rect(cornerRadius:)` is a `RoundedRectangle`.
885
+ - `.rect(cornerRadii:)` is an `UnevenRoundedRectangle` with a radius per corner.
886
+ - Both shape shorthands need iOS 17. On iOS 16, write
887
+ `.clipShape(RoundedRectangle(cornerRadius: 12))`.
1180
888
 
1181
- ## tabItem to Tab (iOS 18+)
889
+ ## 25. tabItem to Tab (iOS 18)
1182
890
 
1183
- The `tabItem` modifier approach was superseded by the `Tab` type inside `TabView` (iOS 18+).
1184
-
1185
- ### Before (Legacy)
891
+ The `Tab` type inside `TabView` (iOS 18+) supersedes the `.tabItem` modifier.
1186
892
 
1187
893
  ```swift
894
+ // Before
1188
895
  TabView {
1189
- HomeView()
1190
- .tabItem {
1191
- Label("Home", systemImage: "house")
1192
- }
1193
- SettingsView()
1194
- .tabItem {
1195
- Label("Settings", systemImage: "gear")
1196
- }
896
+ TodayView().tabItem { Label("Today", systemImage: "sun.max") }
897
+ HistoryView().tabItem { Label("History", systemImage: "clock") }
1197
898
  }
1198
- ```
1199
-
1200
- ### After (Modern - iOS 18+)
1201
899
 
1202
- ```swift
1203
- TabView {
1204
- Tab("Home", systemImage: "house") {
1205
- HomeView()
1206
- }
1207
- Tab("Settings", systemImage: "gear") {
1208
- SettingsView()
900
+ // After
901
+ TabView(selection: $selectedTab) {
902
+ Tab("Today", systemImage: "sun.max", value: .today) { TodayView() }
903
+ Tab("History", systemImage: "clock", value: .history) { HistoryView() }
904
+ TabSection("Collections") {
905
+ Tab("Favorites", systemImage: "star", value: AppTab.favorites) { FavoritesView() }
1209
906
  }
1210
907
  }
1211
908
  ```
1212
909
 
1213
- ### Migration Notes
1214
-
1215
- `Tab` provides a cleaner API and is required for the new tab sidebar on iPadOS 18+. The `tabItem` modifier still works but does not support the sidebar presentation. Use `Tab` with a `value` parameter and `@State` selection for programmatic tab switching. `TabSection` groups tabs in the sidebar.
1216
-
1217
- ---
910
+ - The iPadOS 18 tab sidebar needs `Tab`. `.tabItem` still compiles but its
911
+ tabs cannot appear in the sidebar.
912
+ - Programmatic switching: give each `Tab` a `value` and bind the selection to
913
+ `@State`.
914
+ - `TabSection` groups tabs in the sidebar. Inside it, name the selection type
915
+ (`AppTab.favorites`, where `selectedTab` is an `AppTab`); a bare `.favorites`
916
+ leaves `TabSection`'s generic parameter uninferred and fails to compile.
1218
917
 
1219
- ## scrollIndicators(.hidden) Replaces showsIndicators Parameter
918
+ ## 26. showsIndicators to scrollIndicators
1220
919
 
1221
- The `showsIndicators` parameter on `ScrollView` is available but the `scrollIndicators` modifier (iOS 16+) is preferred for consistency.
1222
-
1223
- ### Before
920
+ `ScrollView(_:showsIndicators:)` still exists, but the `.scrollIndicators`
921
+ modifier (iOS 16+) is the consistent way to control indicators.
1224
922
 
1225
923
  ```swift
1226
- ScrollView(.vertical, showsIndicators: false) {
1227
- content
1228
- }
1229
- ```
1230
-
1231
- ### After (Modern)
924
+ // Before
925
+ ScrollView(.vertical, showsIndicators: false) { timeline }
1232
926
 
1233
- ```swift
1234
- ScrollView {
1235
- content
1236
- }
1237
- .scrollIndicators(.hidden)
927
+ // After
928
+ ScrollView { timeline }
929
+ .scrollIndicators(.hidden)
1238
930
  ```
1239
931
 
1240
- ### Migration Notes
1241
-
1242
- `.scrollIndicators(_:axes:)` accepts `.automatic`, `.visible`, `.hidden`, and `.never`. It also works on `List` and `TextEditor`. The `axes` parameter lets you control horizontal and vertical indicators independently.
932
+ - `.scrollIndicators(_:axes:)` accepts `.automatic`, `.visible`, `.hidden` and
933
+ `.never`.
934
+ - It also affects other scrollable views, such as `List` and `TextEditor`.
935
+ - The `axes` parameter sets horizontal and vertical indicators separately.