Published August 2026 by Batteries Included
Your iOS app is mostly UIKit. Leadership wants SwiftUI. The rewrite pitch says “start clean.” The calendar says you still have to ship.
You do not have to choose between those two stories. Apple documents how UIKit and SwiftUI share the same app. The hard part is deciding which screens move first, how navigation stays stable, and where UIKit should stay on purpose.
This article links factual Apple platform claims to Apple Developer Documentation or Apple Developer videos. Migration sequencing, screen prioritization, and release habits are engineering judgment for shipping apps. They are not Apple requirements.
Short answer
UIKit and SwiftUI can live in the same shipping app. Apple provides hosting controllers, representables, and cell hosting configurations so each framework can embed the other (UIKit integration; Using SwiftUI with UIKit; WWDC22 Use SwiftUI with UIKit).
The practical decision is not whether interop exists. It is which screens to migrate first, how new SwiftUI surfaces enter your existing navigation, and which UIKit islands you leave alone until there is a concrete reason to move them.
On large legacy codebases, the safer default is interop-first migration: adopt SwiftUI where it pays off, keep UIKit where it is already correct, and keep product releases moving. Interop-first is the approach that usually wins when the app must keep shipping. That sequencing is engineering judgment, not an Apple requirement.
Why full rewrites usually lose
A big-bang SwiftUI rewrite freezes shared navigation, deep links, analytics, accessibility, and every screen that still works. You pay rewrite cost up front and defer learning about hosting boundaries until the blast radius is maximum.
Interop-first keeps the UIKit shell shipping while you move isolated or high-churn flows into SwiftUI. You learn the boundary on a small surface, then expand. That is how you keep blast radius small on a brownfield app.
How to prioritize screens
Apple does not publish a single “migrate this screen first” checklist. Score candidate screens on:
- Churn: how often the screen changes.
- Business value: whether SwiftUI speed helps a roadmap goal.
- UI complexity: gestures, accessibility, custom animation, keyboard.
- UIKit coupling: custom layout, video, maps, web views, complex collection layouts, third-party UIKit SDKs.
Safer early candidates: brand-new flows, settings-style screens, and isolated feature areas with clean navigation entry points.
Defer until the team trusts hosting and data boundaries: performance-critical drawing, heavily customized UICollectionView layouts, and SDK surfaces that only expose UIKit.
When to leave UIKit in place
Apple does not publish a “never rewrite this screen” list but here is a good rule of thumb; leave the following in UIKit until there is a concrete product reason to move them:
- Heavily customized collection layouts
- Performance-critical drawing or scrolling paths
- Camera / capture flows that are already stable in UIKit
- Third-party SDKs that only ship UIKit surfaces
Representable and hosting boundaries are where subtle bugs appear if you rush. Leaving a working island in UIKit is often cheaper than a forced port.
Interop building blocks
You only need four platform pieces for an interop-first plan. Treat them as tools with production rules, not as a rewrite checklist.
- UIHostingController: put a SwiftUI screen into a UIKit shell. Present or push it like any other child. Always keep the hosting controller with its view; many SwiftUI features need that view-controller connection (WWDC22).
- UIViewRepresentable / UIViewControllerRepresentable: wrap a UIKit island inside SwiftUI. Pass simple values in and callbacks out. Do not fight SwiftUI for
frame/boundsownership on the managed view. - UIHostingConfiguration: host SwiftUI content in table or collection cells. Prefer this over stuffing a hosting controller into every cell. Features that need a real view-controller hierarchy belong on
UIHostingControllerinstead. - SwiftUI app life cycle: moving
@mainto a SwiftUIAppis a different project from hosting one screen. Master screen-level hosting first.
Hard rules that keep releases safe
- Keep UINavigationController (or your existing coordinator) as the source of truth for stack mutations. Push a
UIHostingControllerthe same way you push any other child. Avoid dual navigation brains (UIKit stack plus a competing SwiftUINavigationStack) unless that boundary is designed on purpose. - Treat every hosted screen as a real child or pushed view controller, not a naked SwiftUI view bolted onto a UIKit hierarchy.
- Define model ownership at the boundary before you add more screens: who creates the model, who retains it across pushes, and who tears it down.
- Ship each new hosting boundary through TestFlight and fix boundary bugs before the next screen. If crashes show up, triage with evidence rather than blaming SwiftUI in the abstract. See our crash triage guide for unfamiliar mobile codebases.
A practical migration sequence
- Pick one low-risk flow with a clean navigation entry.
- Host it with
UIHostingControllerinside your existing UIKit container. - Define model ownership at the boundary before you add more screens.
- Add
UIHostingConfigurationcells only where list content is the win. - Wrap unavoidable UIKit islands with representables; do not rewrite them yet.
- Ship through TestFlight; fix boundary bugs before the next screen.
- Repeat. Only consider a SwiftUI app life-cycle move after the shell is stable.
Need an interop-first iOS migration plan?
We help teams with live UIKit apps adopt SwiftUI screen by screen: hosting boundaries, navigation, and release discipline without a rewrite freeze. If scope needs buy-in before build, start with a Discovery Roadmap.
Related
Sources
- Apple documentation: UIKit integration
- Apple documentation: Using SwiftUI with UIKit
- Apple Developer video: Use SwiftUI with UIKit (WWDC22 10072)
- Apple documentation: UIHostingController
- Apple documentation: UIViewRepresentable
- Apple documentation: UIViewControllerRepresentable
- Apple documentation: UIHostingConfiguration
- Apple documentation: UINavigationController
- Apple documentation: Migrating to the SwiftUI life cycle
- Apple TestFlight