@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md
CHANGED
|
@@ -1,463 +1,310 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Hosting and Incremental Migration
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
A UIKit app does not have to switch to SwiftUI in one release. These six
|
|
4
|
+
patterns move it over piece by piece while both frameworks share the same
|
|
5
|
+
navigation stack, the same models and the same traits.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
1. [Replace whole screens](#1-screen-by-screen-migration)
|
|
10
|
+
2. [Embed a SwiftUI region](#2-uihostingcontroller-as-child)
|
|
11
|
+
3. [Share one navigation stack](#3-navigation-bridging)
|
|
12
|
+
4. [Share models](#4-data-sharing-between-uikit-and-swiftui)
|
|
13
|
+
5. [SwiftUI cells](#5-uihostingconfiguration-ios-16)
|
|
14
|
+
6. [Pass environment and traits](#6-environment-bridging)
|
|
15
15
|
|
|
16
16
|
## 1. Screen-by-Screen Migration
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
### Strategy
|
|
21
|
-
|
|
22
|
-
1. Pick a leaf screen (one that does not contain child view controllers).
|
|
23
|
-
2. Rewrite its UI in SwiftUI.
|
|
24
|
-
3. Replace the UIKit view controller with `UIHostingController` wherever it was instantiated.
|
|
25
|
-
4. Wire navigation from the parent UIKit code into the hosting controller.
|
|
26
|
-
|
|
27
|
-
### Implementation
|
|
28
|
-
|
|
29
|
-
```swift
|
|
30
|
-
// BEFORE: UIKit screen pushed onto a navigation stack
|
|
31
|
-
let detailVC = ItemDetailViewController(item: item)
|
|
32
|
-
navigationController?.pushViewController(detailVC, animated: true)
|
|
33
|
-
|
|
34
|
-
// AFTER: SwiftUI screen wrapped in UIHostingController
|
|
35
|
-
let detailView = ItemDetailView(item: item)
|
|
36
|
-
let hostingVC = UIHostingController(rootView: detailView)
|
|
37
|
-
navigationController?.pushViewController(hostingVC, animated: true)
|
|
38
|
-
```
|
|
18
|
+
The lowest-risk route is to replace whole view controllers, one at a time, with
|
|
19
|
+
a `UIHostingController` that wraps the new SwiftUI screen.
|
|
39
20
|
|
|
40
|
-
|
|
21
|
+
Order of work:
|
|
41
22
|
|
|
42
|
-
|
|
23
|
+
1. Choose a leaf screen, one that embeds no child view controllers.
|
|
24
|
+
2. Rebuild it in SwiftUI.
|
|
25
|
+
3. Replace the old controller wherever it was instantiated.
|
|
26
|
+
4. Connect the surrounding UIKit navigation to the hosting controller.
|
|
43
27
|
|
|
44
28
|
```swift
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
VStack {
|
|
53
|
-
Text(item.title)
|
|
54
|
-
Button("Delete", role: .destructive) {
|
|
55
|
-
onDelete?()
|
|
56
|
-
dismiss()
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// In UIKit:
|
|
63
|
-
let detailView = ItemDetailView(item: item) {
|
|
64
|
-
self.dataSource.delete(item)
|
|
65
|
-
self.navigationController?.popViewController(animated: true)
|
|
66
|
-
}
|
|
67
|
-
let hostingVC = UIHostingController(rootView: detailView)
|
|
29
|
+
// Before: navigationController?.pushViewController(InvoiceDetailViewController(invoice: invoice), animated: true)
|
|
30
|
+
let detail = InvoiceDetailView(invoice: invoice, onVoid: { [weak self] in
|
|
31
|
+
self?.ledger.void(invoice)
|
|
32
|
+
self?.navigationController?.popViewController(animated: true)
|
|
33
|
+
})
|
|
34
|
+
let host = UIHostingController(rootView: detail)
|
|
35
|
+
navigationController?.pushViewController(host, animated: true)
|
|
68
36
|
```
|
|
69
37
|
|
|
70
|
-
|
|
38
|
+
The SwiftUI view takes a callback such as `onVoid`; the UIKit side supplies the
|
|
39
|
+
closure that changes data and pops the screen.
|
|
71
40
|
|
|
72
|
-
|
|
73
|
-
- **Large titles.** Set `hostingVC.navigationItem.largeTitleDisplayMode` in UIKit code if the SwiftUI `.navigationBarTitleDisplayMode()` modifier does not apply correctly.
|
|
74
|
-
- **Tab bar insets.** `UIHostingController` respects `additionalSafeAreaInsets`. If the content overlaps the tab bar, verify safe area propagation.
|
|
41
|
+
Navigation bar behaviour:
|
|
75
42
|
|
|
76
|
-
|
|
43
|
+
- `.navigationTitle()` and `.toolbar()` inside the hosted view show up in the
|
|
44
|
+
UIKit navigation bar. Whether the bar is visible is decided by the parent
|
|
45
|
+
`UINavigationController`.
|
|
46
|
+
- If `.navigationBarTitleDisplayMode()` does not take effect, set
|
|
47
|
+
`host.navigationItem.largeTitleDisplayMode` from UIKit instead.
|
|
48
|
+
- `UIHostingController` respects `additionalSafeAreaInsets`. If content slides
|
|
49
|
+
under the tab bar, check how the safe area reaches the hosted view.
|
|
77
50
|
|
|
78
51
|
## 2. UIHostingController as Child
|
|
79
52
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
### Implementation
|
|
53
|
+
Use a child hosting controller to migrate a region of a screen, such as a
|
|
54
|
+
header, a card or a section, before rewriting the rest.
|
|
83
55
|
|
|
84
56
|
```swift
|
|
85
|
-
final class
|
|
86
|
-
private var
|
|
57
|
+
final class AccountViewController: UIViewController {
|
|
58
|
+
private var balanceHost: UIHostingController<BalanceCard>?
|
|
59
|
+
private let cardContainer = UIView()
|
|
87
60
|
|
|
88
61
|
override func viewDidLoad() {
|
|
89
62
|
super.viewDidLoad()
|
|
90
|
-
|
|
91
|
-
let statsView = StatsCardView(stats: currentStats)
|
|
92
|
-
let hostingVC = UIHostingController(rootView: statsView)
|
|
93
|
-
|
|
94
|
-
// Enable intrinsic sizing so Auto Layout can size the hosted view
|
|
63
|
+
let host = UIHostingController(rootView: BalanceCard(amount: 0))
|
|
95
64
|
if #available(iOS 16.0, *) {
|
|
96
|
-
|
|
65
|
+
host.sizingOptions = [.intrinsicContentSize]
|
|
97
66
|
}
|
|
67
|
+
host.view.backgroundColor = .clear
|
|
98
68
|
|
|
99
|
-
addChild(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
69
|
+
addChild(host)
|
|
70
|
+
host.view.translatesAutoresizingMaskIntoConstraints = false
|
|
71
|
+
cardContainer.addSubview(host.view)
|
|
103
72
|
NSLayoutConstraint.activate([
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
73
|
+
host.view.topAnchor.constraint(equalTo: cardContainer.topAnchor),
|
|
74
|
+
host.view.bottomAnchor.constraint(equalTo: cardContainer.bottomAnchor),
|
|
75
|
+
host.view.leadingAnchor.constraint(equalTo: cardContainer.leadingAnchor),
|
|
76
|
+
host.view.trailingAnchor.constraint(equalTo: cardContainer.trailingAnchor)
|
|
108
77
|
])
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
statsHostingController = hostingVC
|
|
78
|
+
host.didMove(toParent: self)
|
|
79
|
+
balanceHost = host
|
|
112
80
|
}
|
|
113
81
|
|
|
114
|
-
func
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### With `@Observable` Model
|
|
121
|
-
|
|
122
|
-
Pass an `@Observable` model to avoid reassigning `rootView` manually. SwiftUI tracks changes automatically:
|
|
123
|
-
|
|
124
|
-
```swift
|
|
125
|
-
@Observable
|
|
126
|
-
final class DashboardModel {
|
|
127
|
-
var stats: Stats = .empty
|
|
128
|
-
var isLoading = false
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
struct StatsCardView: View {
|
|
132
|
-
let model: DashboardModel
|
|
133
|
-
|
|
134
|
-
var body: some View {
|
|
135
|
-
// Automatically re-renders when model.stats changes
|
|
136
|
-
if model.isLoading {
|
|
137
|
-
ProgressView()
|
|
138
|
-
} else {
|
|
139
|
-
StatsGrid(stats: model.stats)
|
|
140
|
-
}
|
|
82
|
+
func show(amount: Decimal) {
|
|
83
|
+
balanceHost?.rootView = BalanceCard(amount: amount)
|
|
141
84
|
}
|
|
142
85
|
}
|
|
143
|
-
|
|
144
|
-
// In UIKit:
|
|
145
|
-
let model = DashboardModel()
|
|
146
|
-
let hostingVC = UIHostingController(rootView: StatsCardView(model: model))
|
|
147
|
-
|
|
148
|
-
// Later -- just mutate the model, no rootView reassignment needed
|
|
149
|
-
model.stats = newStats
|
|
150
86
|
```
|
|
151
87
|
|
|
152
|
-
|
|
88
|
+
With an `@Observable` model passed into the card instead, mutate the model and
|
|
89
|
+
leave `rootView` alone; SwiftUI picks up the change.
|
|
153
90
|
|
|
154
|
-
|
|
155
|
-
- **sizingOptions on iOS 16+.** Without `.intrinsicContentSize`, the hosted view may report zero size in Auto Layout, causing the container to collapse.
|
|
156
|
-
- **Memory.** Store the hosting controller in a property. If it is only held as a child, removing it from the parent deallocates it and the SwiftUI view disappears.
|
|
91
|
+
Pitfalls:
|
|
157
92
|
|
|
158
|
-
|
|
93
|
+
- By default the hosting view paints the opaque system background. Set
|
|
94
|
+
`view.backgroundColor = .clear` when it sits over existing UIKit content.
|
|
95
|
+
- Without `.intrinsicContentSize` the hosted view can report a zero size and
|
|
96
|
+
the container collapses.
|
|
97
|
+
- Keep a stored reference to the hosting controller. Removing the child would
|
|
98
|
+
otherwise free it and the view vanishes.
|
|
159
99
|
|
|
160
100
|
## 3. Navigation Bridging
|
|
161
101
|
|
|
162
|
-
|
|
102
|
+
UIKit screens and SwiftUI screens can live on the same `UINavigationController`
|
|
103
|
+
stack.
|
|
163
104
|
|
|
164
|
-
|
|
105
|
+
**UIKit pushes SwiftUI.** Wrap the view, give the host a `title`, push it:
|
|
165
106
|
|
|
166
107
|
```swift
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
let hostingVC = UIHostingController(rootView: profileView)
|
|
171
|
-
hostingVC.title = user.name
|
|
172
|
-
navigationController?.pushViewController(hostingVC, animated: true)
|
|
173
|
-
}
|
|
108
|
+
let host = UIHostingController(rootView: ReceiptsView())
|
|
109
|
+
host.title = "Receipts"
|
|
110
|
+
navigationController?.pushViewController(host, animated: true)
|
|
174
111
|
```
|
|
175
112
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
Use a coordinator or `UIViewControllerRepresentable` bridge:
|
|
113
|
+
**SwiftUI presents UIKit.** Present a representable from `.sheet` that returns
|
|
114
|
+
a `UINavigationController(rootViewController:)` around the legacy controller:
|
|
179
115
|
|
|
180
116
|
```swift
|
|
181
|
-
struct
|
|
182
|
-
let user: User
|
|
183
|
-
@State private var showLegacyEditor = false
|
|
184
|
-
|
|
185
|
-
var body: some View {
|
|
186
|
-
List {
|
|
187
|
-
// ... profile content
|
|
188
|
-
Button("Edit (Legacy)") { showLegacyEditor = true }
|
|
189
|
-
}
|
|
190
|
-
.sheet(isPresented: $showLegacyEditor) {
|
|
191
|
-
LegacyEditorWrapper(user: user)
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
struct LegacyEditorWrapper: UIViewControllerRepresentable {
|
|
197
|
-
let user: User
|
|
198
|
-
|
|
117
|
+
struct LegacyExportFlow: UIViewControllerRepresentable {
|
|
199
118
|
func makeUIViewController(context: Context) -> UINavigationController {
|
|
200
|
-
|
|
201
|
-
return UINavigationController(rootViewController: editor)
|
|
119
|
+
UINavigationController(rootViewController: ExportOptionsViewController())
|
|
202
120
|
}
|
|
203
|
-
|
|
204
|
-
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
|
|
121
|
+
func updateUIViewController(_ nav: UINavigationController, context: Context) {}
|
|
205
122
|
}
|
|
206
|
-
```
|
|
207
123
|
|
|
208
|
-
|
|
124
|
+
// .sheet(isPresented: $exporting) { LegacyExportFlow() }
|
|
125
|
+
```
|
|
209
126
|
|
|
210
|
-
|
|
127
|
+
**Deep integration.** Give SwiftUI views a small bridge to the UIKit stack
|
|
128
|
+
through the environment:
|
|
211
129
|
|
|
212
130
|
```swift
|
|
213
|
-
struct
|
|
131
|
+
struct UIKitNavigator {
|
|
214
132
|
weak var navigationController: UINavigationController?
|
|
215
133
|
|
|
216
|
-
|
|
217
|
-
|
|
134
|
+
@MainActor
|
|
135
|
+
func push(_ controller: UIViewController, animated: Bool = true) {
|
|
136
|
+
navigationController?.pushViewController(controller, animated: animated)
|
|
218
137
|
}
|
|
219
138
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
139
|
+
@MainActor
|
|
140
|
+
func push<Content: View>(_ view: Content, title: String, animated: Bool = true) {
|
|
141
|
+
let host = UIHostingController(rootView: view)
|
|
142
|
+
host.title = title
|
|
143
|
+
navigationController?.pushViewController(host, animated: animated)
|
|
224
144
|
}
|
|
225
145
|
}
|
|
226
146
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
static let defaultValue = NavigationBridge()
|
|
147
|
+
private struct UIKitNavigatorKey: EnvironmentKey {
|
|
148
|
+
static var defaultValue: UIKitNavigator { UIKitNavigator() }
|
|
230
149
|
}
|
|
231
150
|
|
|
232
151
|
extension EnvironmentValues {
|
|
233
|
-
var
|
|
234
|
-
get { self[
|
|
235
|
-
set { self[
|
|
152
|
+
var uikitNavigator: UIKitNavigator {
|
|
153
|
+
get { self[UIKitNavigatorKey.self] }
|
|
154
|
+
set { self[UIKitNavigatorKey.self] = newValue }
|
|
236
155
|
}
|
|
237
156
|
}
|
|
238
157
|
```
|
|
239
158
|
|
|
240
|
-
|
|
159
|
+
Set it on the root view before creating the host:
|
|
160
|
+
`ReceiptsView().environment(\.uikitNavigator, UIKitNavigator(navigationController: navigationController))`.
|
|
241
161
|
|
|
242
|
-
|
|
243
|
-
- **Double navigation bars.** If the SwiftUI view uses `NavigationStack`, it creates its own navigation bar inside the UIKit one. Remove `NavigationStack` from SwiftUI views presented inside `UINavigationController`.
|
|
244
|
-
- **Toolbar items.** SwiftUI `.toolbar` items propagate to the UIKit navigation bar when hosted in `UIHostingController`. This works reliably on iOS 16+.
|
|
162
|
+
Notes:
|
|
245
163
|
|
|
246
|
-
|
|
164
|
+
- A pushed hosting controller gets the standard back button automatically. Do
|
|
165
|
+
not add your own.
|
|
166
|
+
- Do not put a `NavigationStack` inside a hosting controller that is already on
|
|
167
|
+
a `UINavigationController`; you get two navigation bars. Remove the
|
|
168
|
+
`NavigationStack`.
|
|
169
|
+
- On iOS 16 and later, SwiftUI `.toolbar` items reliably appear in the UIKit
|
|
170
|
+
navigation bar.
|
|
247
171
|
|
|
248
172
|
## 4. Data Sharing Between UIKit and SwiftUI
|
|
249
173
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
The cleanest approach. Create an `@Observable` model, pass it to both UIKit and SwiftUI code:
|
|
174
|
+
**Preferred (iOS 17+): one `@Observable` model for both sides.** SwiftUI tracks
|
|
175
|
+
it automatically; UIKit reads its properties directly.
|
|
253
176
|
|
|
254
177
|
```swift
|
|
255
178
|
@Observable
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
var
|
|
259
|
-
var theme: AppTheme = .system
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// UIKit side -- read properties directly
|
|
263
|
-
let state = AppState()
|
|
264
|
-
func viewDidLoad() {
|
|
265
|
-
titleLabel.text = state.currentUser?.name
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// SwiftUI side -- observation is automatic
|
|
269
|
-
struct HeaderView: View {
|
|
270
|
-
let state: AppState
|
|
271
|
-
|
|
272
|
-
var body: some View {
|
|
273
|
-
HStack {
|
|
274
|
-
Text(state.currentUser?.name ?? "Guest")
|
|
275
|
-
if state.unreadCount > 0 {
|
|
276
|
-
Badge(count: state.unreadCount)
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
179
|
+
@MainActor
|
|
180
|
+
final class CartModel {
|
|
181
|
+
var itemCount = 0
|
|
280
182
|
}
|
|
281
183
|
```
|
|
282
184
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
185
|
+
`@Observable` does not push changes into UIKit by itself. A UIKit controller
|
|
186
|
+
has to observe explicitly, for example with `withObservationTracking` inside a
|
|
187
|
+
long-lived task:
|
|
286
188
|
|
|
287
189
|
```swift
|
|
288
|
-
|
|
289
|
-
|
|
190
|
+
final class CartBadgeViewController: UIViewController {
|
|
191
|
+
private let cart: CartModel
|
|
192
|
+
private var observation: Task<Void, Never>?
|
|
290
193
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
194
|
+
init(cart: CartModel) {
|
|
195
|
+
self.cart = cart
|
|
196
|
+
super.init(nibName: nil, bundle: .main)
|
|
197
|
+
}
|
|
198
|
+
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
|
|
294
199
|
|
|
295
200
|
override func viewDidLoad() {
|
|
296
201
|
super.viewDidLoad()
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
private func startObserving() {
|
|
301
|
-
observationTask = Task { @MainActor [weak self] in
|
|
202
|
+
observation = Task { @MainActor [weak self, cart] in
|
|
302
203
|
while !Task.isCancelled {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
204
|
+
await withCheckedContinuation { continuation in
|
|
205
|
+
withObservationTracking {
|
|
206
|
+
self?.title = "\(cart.itemCount) items"
|
|
207
|
+
} onChange: {
|
|
208
|
+
continuation.resume()
|
|
209
|
+
}
|
|
308
210
|
}
|
|
309
|
-
try? await Task.sleep(for: .zero) // Yield to allow onChange to fire
|
|
310
211
|
}
|
|
311
212
|
}
|
|
312
213
|
}
|
|
313
214
|
|
|
314
|
-
|
|
315
|
-
badgeLabel.text = "\(unreadCount)"
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
deinit { observationTask?.cancel() }
|
|
215
|
+
deinit { observation?.cancel() }
|
|
319
216
|
}
|
|
320
217
|
```
|
|
321
218
|
|
|
322
|
-
|
|
219
|
+
Each pass applies the current value, registers tracking, and suspends until
|
|
220
|
+
the next change. A variant that loops and yields with `Task.sleep(for: .zero)`
|
|
221
|
+
also works, but it wakes constantly; waiting on the `onChange` signal does not.
|
|
222
|
+
The task holds `self` weakly, and `deinit` cancels it; a cancelled task leaves the loop at the next change.
|
|
323
223
|
|
|
324
|
-
|
|
224
|
+
**Legacy (iOS 15 and 16, or an `ObservableObject`).** Subscribe to
|
|
225
|
+
`objectWillChange` with Combine and keep the cancellable:
|
|
325
226
|
|
|
326
227
|
```swift
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
override func viewDidLoad() {
|
|
332
|
-
super.viewDidLoad()
|
|
333
|
-
cancellable = settings.objectWillChange
|
|
334
|
-
.receive(on: RunLoop.main)
|
|
335
|
-
.sink { [weak self] _ in
|
|
336
|
-
self?.updateUI()
|
|
337
|
-
}
|
|
228
|
+
cancellable = store.objectWillChange
|
|
229
|
+
.receive(on: RunLoop.main)
|
|
230
|
+
.sink { [weak self] _ in
|
|
231
|
+
self?.refreshBadge()
|
|
338
232
|
}
|
|
339
|
-
}
|
|
340
233
|
```
|
|
341
234
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
- **`@Observable` does not trigger UIKit updates automatically.** Unlike SwiftUI views, UIKit code must manually observe changes via `withObservationTracking` or `Combine`.
|
|
345
|
-
- **Thread safety.** Mutate `@Observable` properties on `@MainActor` when they drive UI in both UIKit and SwiftUI.
|
|
346
|
-
- **Retain cycles.** Use `[weak self]` in Combine sinks and task closures. Store cancellables and tasks, then cancel in `deinit`.
|
|
235
|
+
Rules:
|
|
347
236
|
|
|
348
|
-
|
|
237
|
+
- Change UI-driving properties of an `@Observable` model on the `@MainActor`.
|
|
238
|
+
- Capture `[weak self]` in sinks and task closures. Keep cancellables and tasks
|
|
239
|
+
in stored properties and cancel them in `deinit`.
|
|
349
240
|
|
|
350
241
|
## 5. UIHostingConfiguration (iOS 16+)
|
|
351
242
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
### UICollectionView with SwiftUI Cells
|
|
243
|
+
This is the preferred way to put SwiftUI content into UIKit collection and
|
|
244
|
+
table view cells.
|
|
355
245
|
|
|
356
246
|
```swift
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
withReuseIdentifier: "cell",
|
|
364
|
-
for: indexPath
|
|
365
|
-
)
|
|
366
|
-
let item = dataSource[indexPath.item]
|
|
367
|
-
|
|
368
|
-
cell.contentConfiguration = UIHostingConfiguration {
|
|
369
|
-
HStack {
|
|
370
|
-
AsyncImage(url: item.imageURL) { image in
|
|
371
|
-
image.resizable().scaledToFill()
|
|
372
|
-
} placeholder: {
|
|
373
|
-
ProgressView()
|
|
374
|
-
}
|
|
375
|
-
.frame(width: 60, height: 60)
|
|
376
|
-
.clipShape(.rect(cornerRadius: 8))
|
|
377
|
-
|
|
378
|
-
VStack(alignment: .leading) {
|
|
379
|
-
Text(item.title).font(.headline)
|
|
380
|
-
Text(item.subtitle).font(.subheadline).foregroundStyle(.secondary)
|
|
381
|
-
}
|
|
247
|
+
cell.contentConfiguration = UIHostingConfiguration {
|
|
248
|
+
HStack(spacing: 12) {
|
|
249
|
+
AsyncImage(url: product.thumbnailURL) { image in
|
|
250
|
+
image.resizable().scaledToFill()
|
|
251
|
+
} placeholder: {
|
|
252
|
+
ProgressView()
|
|
382
253
|
}
|
|
254
|
+
.frame(width: 56, height: 56)
|
|
255
|
+
.clipShape(.rect(cornerRadius: 8))
|
|
256
|
+
Text(product.name)
|
|
383
257
|
}
|
|
384
|
-
.margins(.all, 12)
|
|
385
|
-
|
|
386
|
-
return cell
|
|
387
258
|
}
|
|
259
|
+
.margins(.all, 12)
|
|
388
260
|
```
|
|
389
261
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
```swift
|
|
393
|
-
@available(iOS 16.0, *)
|
|
394
|
-
func tableView(
|
|
395
|
-
_ tableView: UITableView,
|
|
396
|
-
cellForRowAt indexPath: IndexPath
|
|
397
|
-
) -> UITableViewCell {
|
|
398
|
-
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
|
|
399
|
-
let item = items[indexPath.row]
|
|
400
|
-
|
|
401
|
-
cell.contentConfiguration = UIHostingConfiguration {
|
|
402
|
-
ItemRowView(item: item)
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
return cell
|
|
406
|
-
}
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
### Self-Sizing
|
|
410
|
-
|
|
411
|
-
`UIHostingConfiguration` cells self-size automatically. Ensure:
|
|
412
|
-
- The table/collection view uses `UICollectionViewCompositionalLayout` with estimated dimensions, or `tableView.rowHeight = UITableView.automaticDimension`.
|
|
413
|
-
- The SwiftUI content has defined height (via content or explicit `.frame`).
|
|
414
|
-
|
|
415
|
-
### Background Customization
|
|
262
|
+
Table views work the same way inside `tableView(_:cellForRowAt:)`:
|
|
416
263
|
|
|
417
264
|
```swift
|
|
265
|
+
let cell = tableView.dequeueReusableCell(withIdentifier: "order", for: indexPath)
|
|
418
266
|
cell.contentConfiguration = UIHostingConfiguration {
|
|
419
|
-
|
|
267
|
+
OrderRowView(order: orders[indexPath.row])
|
|
420
268
|
}
|
|
421
|
-
|
|
422
|
-
RoundedRectangle(cornerRadius: 12)
|
|
423
|
-
.fill(.background)
|
|
424
|
-
}
|
|
425
|
-
.margins(.horizontal, 16)
|
|
426
|
-
.minSize(height: 60)
|
|
269
|
+
return cell
|
|
427
270
|
```
|
|
428
271
|
|
|
429
|
-
|
|
272
|
+
Configuration modifiers include `.background { }`, `.margins(.horizontal, 16)`
|
|
273
|
+
and `.minSize(height: 60)`.
|
|
430
274
|
|
|
431
|
-
|
|
432
|
-
- **State management.** The SwiftUI content inside `UIHostingConfiguration` is recreated on each cell reuse. Do not store `@State` that needs to persist across reuse -- use the data model instead.
|
|
433
|
-
- **Swipe actions.** Configure swipe actions in UIKit (`leadingSwipeActionsConfigurationForRowAt`), not inside the SwiftUI content.
|
|
434
|
-
- **No `@Environment` propagation by default.** Environment values from the UIKit context are not automatically available. Inject them explicitly in the `UIHostingConfiguration` closure.
|
|
275
|
+
Notes:
|
|
435
276
|
|
|
436
|
-
|
|
277
|
+
- Self-sizing needs a `UICollectionViewCompositionalLayout` with estimated
|
|
278
|
+
dimensions (for tables, `rowHeight` set to `UITableView.automaticDimension`), plus
|
|
279
|
+
SwiftUI content that has a definite height.
|
|
280
|
+
- Behind every configuration sits a small hosting controller. For huge
|
|
281
|
+
lists (10,000 items and up) measure with Instruments.
|
|
282
|
+
- The content is rebuilt when a cell is reused. `@State` inside it does not
|
|
283
|
+
survive reuse; keep such state in the data model.
|
|
284
|
+
- Swipe actions belong to UIKit (`leadingSwipeActionsConfigurationForRowAt`),
|
|
285
|
+
not to the SwiftUI content.
|
|
286
|
+
- Environment values from the surrounding UIKit context are not passed in
|
|
287
|
+
automatically. Inject what you need inside the configuration closure.
|
|
437
288
|
|
|
438
289
|
## 6. Environment Bridging
|
|
439
290
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
291
|
+
Environment has to be attached before the hosting controller exists. Apply
|
|
292
|
+
`.environment(model)` (iOS 17, for an `@Observable` model),
|
|
293
|
+
`.environment(\.locale, ...)` and similar modifiers to the root view, then
|
|
294
|
+
create the host. Adding environment afterwards means
|
|
295
|
+
assigning a new `rootView`.
|
|
443
296
|
|
|
444
297
|
```swift
|
|
445
|
-
let
|
|
446
|
-
|
|
447
|
-
.environment(
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
let hostingVC = UIHostingController(rootView: settingsView)
|
|
298
|
+
let root = SettingsView()
|
|
299
|
+
.environment(session)
|
|
300
|
+
.environment(\.locale, Locale(identifier: "fr_FR"))
|
|
301
|
+
let host = UIHostingController(rootView: root)
|
|
451
302
|
```
|
|
452
303
|
|
|
453
|
-
|
|
304
|
+
Traits that bridge on their own:
|
|
454
305
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
`UIHostingController` automatically bridges these UIKit trait collections to SwiftUI environment values:
|
|
458
|
-
|
|
459
|
-
| UIKit Trait | SwiftUI Environment |
|
|
460
|
-
|------------|-------------------|
|
|
306
|
+
| UIKit trait | SwiftUI environment |
|
|
307
|
+
|-------------|---------------------|
|
|
461
308
|
| `userInterfaceStyle` | `\.colorScheme` |
|
|
462
309
|
| `horizontalSizeClass` | `\.horizontalSizeClass` |
|
|
463
310
|
| `verticalSizeClass` | `\.verticalSizeClass` |
|
|
@@ -465,70 +312,40 @@ Apply environment modifiers to the root view before passing it to the hosting co
|
|
|
465
312
|
| `layoutDirection` | `\.layoutDirection` |
|
|
466
313
|
| `legibilityWeight` | `\.legibilityWeight` |
|
|
467
314
|
|
|
468
|
-
These
|
|
315
|
+
These stay current as the device rotates, a split view resizes, or an
|
|
316
|
+
accessibility setting changes. `host.overrideUserInterfaceStyle` forces light
|
|
317
|
+
or dark and flows through to `\.colorScheme`.
|
|
469
318
|
|
|
470
|
-
|
|
319
|
+
**Custom values.** Declare an `EnvironmentKey` with a `defaultValue`, extend
|
|
320
|
+
`EnvironmentValues`, set it from UIKit with `.environment(\.yourKey, value)`
|
|
321
|
+
and read it in SwiftUI with `@Environment(\.yourKey)`. The navigator in pattern
|
|
322
|
+
3 is an example.
|
|
471
323
|
|
|
472
|
-
|
|
324
|
+
**Changing environment after creation (iOS 17+).** Wrap the content in a
|
|
325
|
+
container view that reads an `@Observable` state object and applies the environment. Mutating
|
|
326
|
+
that object then updates the environment without touching `rootView`.
|
|
473
327
|
|
|
474
328
|
```swift
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
extension EnvironmentValues {
|
|
480
|
-
var userRole: UserRole {
|
|
481
|
-
get { self[UserRoleKey.self] }
|
|
482
|
-
set { self[UserRoleKey.self] = newValue }
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// UIKit side:
|
|
487
|
-
let role = authManager.currentRole
|
|
488
|
-
let profileView = ProfileView().environment(\.userRole, role)
|
|
489
|
-
let hostingVC = UIHostingController(rootView: profileView)
|
|
490
|
-
|
|
491
|
-
// SwiftUI side:
|
|
492
|
-
struct ProfileView: View {
|
|
493
|
-
@Environment(\.userRole) private var role
|
|
494
|
-
|
|
495
|
-
var body: some View {
|
|
496
|
-
if role == .admin {
|
|
497
|
-
AdminDashboard()
|
|
498
|
-
} else {
|
|
499
|
-
UserDashboard()
|
|
500
|
-
}
|
|
501
|
-
}
|
|
329
|
+
@Observable
|
|
330
|
+
@MainActor
|
|
331
|
+
final class HostedEnvironment {
|
|
332
|
+
var locale = Locale.current
|
|
502
333
|
}
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
### Updating Environment After Creation
|
|
506
|
-
|
|
507
|
-
To change environment values after the hosting controller is created, wrap the root view in a container that takes a binding or observable:
|
|
508
334
|
|
|
509
|
-
```swift
|
|
510
335
|
struct EnvironmentBridge<Content: View>: View {
|
|
511
|
-
let state:
|
|
512
|
-
|
|
336
|
+
let state: HostedEnvironment
|
|
337
|
+
@ViewBuilder var content: Content
|
|
513
338
|
|
|
514
339
|
var body: some View {
|
|
515
|
-
content
|
|
516
|
-
.environment(state)
|
|
517
|
-
.environment(\.userRole, state.currentRole)
|
|
340
|
+
content.environment(\.locale, state.locale)
|
|
518
341
|
}
|
|
519
342
|
}
|
|
520
|
-
|
|
521
|
-
// UIKit:
|
|
522
|
-
let state = AppState()
|
|
523
|
-
let bridge = EnvironmentBridge(state: state, content: SettingsView())
|
|
524
|
-
let hostingVC = UIHostingController(rootView: bridge)
|
|
525
|
-
|
|
526
|
-
// Later: mutating state.currentRole updates the environment automatically
|
|
527
|
-
state.currentRole = .admin
|
|
528
343
|
```
|
|
529
344
|
|
|
530
|
-
|
|
345
|
+
Pitfalls:
|
|
531
346
|
|
|
532
|
-
-
|
|
533
|
-
|
|
534
|
-
|
|
347
|
+
- `@Environment(\.dismiss)` inside a hosted view works when the host was shown
|
|
348
|
+
with `present(_:animated:)`. It does nothing when the host was pushed onto a
|
|
349
|
+
`UINavigationController`; call `popViewController(animated:)` there instead.
|
|
350
|
+
- A required environment object that is missing crashes at runtime. Set every
|
|
351
|
+
one before creating the hosting controller.
|