SwiftUI ⇄ UIKit Interop
This skill covers the boundary between SwiftUI and UIKit — embedding one inside the other and passing data across. It does not cover pure-SwiftUI view design (use swiftui-expert) or the app's dependency graph (use ios-architecture-expert).
Agent Behavior Contract
When this skill is active, follow these rules strictly:
- This skill is only about the boundary. Pure SwiftUI view code → defer to
swiftui-expert; app architecture / composition root / DI → defer to ios-architecture-expert.
- Wrap UIKit in SwiftUI with a representable —
UIViewRepresentable for a view, UIViewControllerRepresentable for a view controller. Never new up a UIKit view directly inside a SwiftUI body.
- Route all UIKit delegate / data-source / target-action callbacks through a
Coordinator (makeCoordinator()), never store mutable UIKit state on the representable struct.
- Data flows one way per direction: SwiftUI state →
updateUIView/updateUIViewController; UIKit events → Coordinator → @Binding/closure. Don't mutate SwiftUI state synchronously from inside updateUIView.
- Embed SwiftUI in UIKit with
UIHostingController — and when embedding (not presenting), add it as a child view controller (addChild / didMove(toParent:)), not just its view.
- Use
UIHostingConfiguration for SwiftUI in collection/table cells (iOS 16+) — one configuration per cell, not a UIHostingController per cell.
- Keep hosting sizing correct — set
sizingOptions / honor intrinsicContentSize / safeAreaRegions so Auto Layout and self-sizing cells measure the SwiftUI content.
- Gate version-specific APIs (
UIHostingConfiguration iOS 16, UIHostingController.sizingOptions iOS 16, trait-bridged environment iOS 17, @UIApplicationDelegateAdaptor lifecycle) with #available/@available.
Interop Diagnostic Table
| Symptom |
First check |
Smallest fix |
Deep dive |
| Show a SwiftUI screen from a UIKit view controller |
Present vs embed |
Present a UIHostingController |
references/hosting-controller.md |
| Embed a SwiftUI view inside an existing UIKit screen |
Child VC containment |
Add UIHostingController as a child VC |
references/hosting-controller.md |
| SwiftUI content in a collection/table cell |
Cell configuration |
UIHostingConfiguration (iOS 16+) |
references/hosting-configuration.md |
| Use a UIKit view (map, camera, web) in SwiftUI |
View wrapping |
UIViewRepresentable + Coordinator |
references/representables.md |
| Use a UIKit view controller in SwiftUI |
VC wrapping |
UIViewControllerRepresentable + Coordinator |
references/representables.md |
| Delegate/data-source callbacks from wrapped UIKit |
Callback routing |
Route through the Coordinator |
references/representables.md |
| Pass data/state between SwiftUI and UIKit |
Direction of flow |
init params + @Binding/closures; forward environment via traits |
references/passing-data-across-the-boundary.md |
| Hosted SwiftUI doesn't resize / clips / fights Auto Layout |
Hosting sizing |
sizingOptions / intrinsic size / safe-area |
references/hosting-controller.md |
| SwiftUI in a widget or Live Activity |
Extension target |
WidgetKit + SwiftUI |
references/widgets-and-live-activities.md |
| Move a UIKit app onto the SwiftUI lifecycle |
Incremental migration |
Adopt App/Scene, bridge with delegate adaptors |
references/lifecycle-migration.md |
Direction Decision Tree
- Which framework owns the screen you're adding to?
- UIKit owns it, you're adding SwiftUI → embed (
UIHostingController, or UIHostingConfiguration for cells)
- SwiftUI owns it, you need a UIKit view/VC → wrap (
UIViewRepresentable / UIViewControllerRepresentable)
- Is it a whole screen or a piece? Whole screen → present/push a hosting controller or a representable VC. A piece → child-embed a hosting controller, or wrap a single UIKit view.
- Does the UIKit side report events back? → add a
Coordinator and bridge via @Binding/closures.
- Crossing the lifecycle? Migrating the app entry point →
references/lifecycle-migration.md.
Guardrails
- Do not instantiate UIKit views/VCs directly in a SwiftUI
body — wrap them in a representable
- Do not put delegate/data-source conformance on the representable struct — use the
Coordinator (a class)
- Do not mutate
@State/@Binding synchronously inside updateUIView/updateUIViewController (causes update loops) — dispatch or guard for actual change
- Do not add a hosting controller's
view as a subview without also doing child-VC containment (addChild/didMove) — you lose lifecycle and safe-area forwarding
- Do not create a
UIHostingController per cell — use UIHostingConfiguration
- Do not solve app architecture/DI here — that is
ios-architecture-expert's job
- Do not re-teach pure SwiftUI patterns — defer to
swiftui-expert
Reference Router
Open the smallest reference that matches the task:
- Embedding SwiftUI in UIKit
- hosting-controller.md —
UIHostingController: present, child-embed, subclass, sizing
- hosting-configuration.md —
UIHostingConfiguration for collection/table cells (iOS 16+)
- Wrapping UIKit in SwiftUI
- representables.md —
UIViewRepresentable, UIViewControllerRepresentable, the Coordinator pattern
- Crossing the boundary
- passing-data-across-the-boundary.md — init params,
@Binding, closures, environment/trait forwarding, observation
- Extensions
- widgets-and-live-activities.md — SwiftUI in WidgetKit widgets and Live Activities
- Migration
- lifecycle-migration.md — moving a UIKit
AppDelegate/SceneDelegate app to the SwiftUI App lifecycle
1---2name: swiftui-uikit-interop3description: Use this skill to bridge SwiftUI and UIKit in either direction. Embed SwiftUI in a UIKit app -- present or child-embed a UIHostingController, put SwiftUI in UICollectionView/UITableView cells via UIHostingConfiguration, or build a widget / Live Activity. Wrap UIKit for use in SwiftUI -- UIViewRepresentable / UIViewControllerRepresentable with a Coordinator for delegates and data sources. Pass data and state across the boundary (init params, @Binding, closures, environment/trait forwarding), fix hosting-controller sizing, and migrate a UIKit AppDelegate/SceneDelegate app incrementally to the SwiftUI App lifecycle. Trigger even when the user doesn't say "interop" -- e.g. "show this SwiftUI screen from my view controller", "use my UIKit map view in SwiftUI", "put SwiftUI in a collection view cell". Do NOT use for pure SwiftUI view code (swiftui-expert), app architecture / composition root (ios-architecture-expert), or general Swift language questions (swift-language-expert).4---56# SwiftUI ⇄ UIKit Interop78This skill covers the **boundary** between SwiftUI and UIKit — embedding one inside the other and passing data across. It does not cover pure-SwiftUI view design (use `swiftui-expert`) or the app's dependency graph (use `ios-architecture-expert`).910## Agent Behavior Contract1112When this skill is active, follow these rules **strictly**:13141. **This skill is only about the boundary.** Pure SwiftUI view code → defer to `swiftui-expert`; app architecture / composition root / DI → defer to `ios-architecture-expert`.152. **Wrap UIKit in SwiftUI with a representable** — `UIViewRepresentable` for a view, `UIViewControllerRepresentable` for a view controller. Never new up a UIKit view directly inside a SwiftUI `body`.163. **Route all UIKit delegate / data-source / target-action callbacks through a `Coordinator`** (`makeCoordinator()`), never store mutable UIKit state on the representable struct.174. **Data flows one way per direction:** SwiftUI state → `updateUIView`/`updateUIViewController`; UIKit events → `Coordinator` → `@Binding`/closure. Don't mutate SwiftUI state synchronously from inside `updateUIView`.185. **Embed SwiftUI in UIKit with `UIHostingController`** — and when embedding (not presenting), add it as a **child view controller** (`addChild` / `didMove(toParent:)`), not just its `view`.196. **Use `UIHostingConfiguration` for SwiftUI in collection/table cells** (iOS 16+) — one configuration per cell, not a `UIHostingController` per cell.207. **Keep hosting sizing correct** — set `sizingOptions` / honor `intrinsicContentSize` / `safeAreaRegions` so Auto Layout and self-sizing cells measure the SwiftUI content.218. **Gate version-specific APIs** (`UIHostingConfiguration` iOS 16, `UIHostingController.sizingOptions` iOS 16, trait-bridged environment iOS 17, `@UIApplicationDelegateAdaptor` lifecycle) with `#available`/`@available`.2223---2425## Interop Diagnostic Table2627| Symptom | First check | Smallest fix | Deep dive |28|---|---|---|---|29| Show a SwiftUI screen from a UIKit view controller | Present vs embed | Present a `UIHostingController` | `references/hosting-controller.md` |30| Embed a SwiftUI view inside an existing UIKit screen | Child VC containment | Add `UIHostingController` as a child VC | `references/hosting-controller.md` |31| SwiftUI content in a collection/table cell | Cell configuration | `UIHostingConfiguration` (iOS 16+) | `references/hosting-configuration.md` |32| Use a UIKit view (map, camera, web) in SwiftUI | View wrapping | `UIViewRepresentable` + `Coordinator` | `references/representables.md` |33| Use a UIKit view controller in SwiftUI | VC wrapping | `UIViewControllerRepresentable` + `Coordinator` | `references/representables.md` |34| Delegate/data-source callbacks from wrapped UIKit | Callback routing | Route through the `Coordinator` | `references/representables.md` |35| Pass data/state between SwiftUI and UIKit | Direction of flow | init params + `@Binding`/closures; forward environment via traits | `references/passing-data-across-the-boundary.md` |36| Hosted SwiftUI doesn't resize / clips / fights Auto Layout | Hosting sizing | `sizingOptions` / intrinsic size / safe-area | `references/hosting-controller.md` |37| SwiftUI in a widget or Live Activity | Extension target | WidgetKit + SwiftUI | `references/widgets-and-live-activities.md` |38| Move a UIKit app onto the SwiftUI lifecycle | Incremental migration | Adopt `App`/`Scene`, bridge with delegate adaptors | `references/lifecycle-migration.md` |3940---4142## Direction Decision Tree43441. **Which framework owns the screen you're adding to?**45 - UIKit owns it, you're adding SwiftUI → **embed** (`UIHostingController`, or `UIHostingConfiguration` for cells)46 - SwiftUI owns it, you need a UIKit view/VC → **wrap** (`UIViewRepresentable` / `UIViewControllerRepresentable`)472. **Is it a whole screen or a piece?** Whole screen → present/push a hosting controller or a representable VC. A piece → child-embed a hosting controller, or wrap a single UIKit view.483. **Does the UIKit side report events back?** → add a `Coordinator` and bridge via `@Binding`/closures.494. **Crossing the lifecycle?** Migrating the app entry point → `references/lifecycle-migration.md`.5051---5253## Guardrails5455- Do not instantiate UIKit views/VCs directly in a SwiftUI `body` — wrap them in a representable56- Do not put delegate/data-source conformance on the representable struct — use the `Coordinator` (a class)57- Do not mutate `@State`/`@Binding` synchronously inside `updateUIView`/`updateUIViewController` (causes update loops) — dispatch or guard for actual change58- Do not add a hosting controller's `view` as a subview without also doing child-VC containment (`addChild`/`didMove`) — you lose lifecycle and safe-area forwarding59- Do not create a `UIHostingController` per cell — use `UIHostingConfiguration`60- Do not solve app architecture/DI here — that is `ios-architecture-expert`'s job61- Do not re-teach pure SwiftUI patterns — defer to `swiftui-expert`6263---6465## Reference Router6667Open the smallest reference that matches the task:6869- **Embedding SwiftUI in UIKit**70 - [hosting-controller.md](references/hosting-controller.md) — `UIHostingController`: present, child-embed, subclass, sizing71 - [hosting-configuration.md](references/hosting-configuration.md) — `UIHostingConfiguration` for collection/table cells (iOS 16+)72- **Wrapping UIKit in SwiftUI**73 - [representables.md](references/representables.md) — `UIViewRepresentable`, `UIViewControllerRepresentable`, the `Coordinator` pattern74- **Crossing the boundary**75 - [passing-data-across-the-boundary.md](references/passing-data-across-the-boundary.md) — init params, `@Binding`, closures, environment/trait forwarding, observation76- **Extensions**77 - [widgets-and-live-activities.md](references/widgets-and-live-activities.md) — SwiftUI in WidgetKit widgets and Live Activities78- **Migration**79 - [lifecycle-migration.md](references/lifecycle-migration.md) — moving a UIKit `AppDelegate`/`SceneDelegate` app to the SwiftUI `App` lifecycle