@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,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.