# Swiftui Uikit Interop

> 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).

- Skill: `swiftyjourney/swiftui-uikit-interop` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add swiftyjourney/swiftui-uikit-interop`
- Raw SKILL.md: https://api.skillmd.com/api/skills/swiftyjourney/swiftui-uikit-interop/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: SwiftyJourney (https://skillmd.com/u/swiftyjourney)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/swiftyjourney/swiftui-uikit-interop

---


# 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**:

1. **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`.
2. **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`.
3. **Route all UIKit delegate / data-source / target-action callbacks through a `Coordinator`** (`makeCoordinator()`), never store mutable UIKit state on the representable struct.
4. **Data flows one way per direction:** SwiftUI state → `updateUIView`/`updateUIViewController`; UIKit events → `Coordinator` → `@Binding`/closure. Don't mutate SwiftUI state synchronously from inside `updateUIView`.
5. **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`.
6. **Use `UIHostingConfiguration` for SwiftUI in collection/table cells** (iOS 16+) — one configuration per cell, not a `UIHostingController` per cell.
7. **Keep hosting sizing correct** — set `sizingOptions` / honor `intrinsicContentSize` / `safeAreaRegions` so Auto Layout and self-sizing cells measure the SwiftUI content.
8. **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

1. **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`)
2. **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.
3. **Does the UIKit side report events back?** → add a `Coordinator` and bridge via `@Binding`/closures.
4. **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](references/hosting-controller.md) — `UIHostingController`: present, child-embed, subclass, sizing
  - [hosting-configuration.md](references/hosting-configuration.md) — `UIHostingConfiguration` for collection/table cells (iOS 16+)
- **Wrapping UIKit in SwiftUI**
  - [representables.md](references/representables.md) — `UIViewRepresentable`, `UIViewControllerRepresentable`, the `Coordinator` pattern
- **Crossing the boundary**
  - [passing-data-across-the-boundary.md](references/passing-data-across-the-boundary.md) — init params, `@Binding`, closures, environment/trait forwarding, observation
- **Extensions**
  - [widgets-and-live-activities.md](references/widgets-and-live-activities.md) — SwiftUI in WidgetKit widgets and Live Activities
- **Migration**
  - [lifecycle-migration.md](references/lifecycle-migration.md) — moving a UIKit `AppDelegate`/`SceneDelegate` app to the SwiftUI `App` lifecycle

