Intermixing Navigation with SwiftUI and UIKit

I've been adopting SwiftUI into a legacy UIKit app. Individual views work great, but when I try to intermix SwiftUI and UIKit navigation things go bad fast. What is the best practice here?

Answered by Frameworks Engineer in 892618022

Thanks for the question!

My recommendation would be to not intermix SwiftUI and UIKit navigation within the same ‘stack’ (NavigationStack in SwiftUI, UINavigationController in UIKit) - having the 2 frameworks both trying to mutate the same stack can run into issues. (Apps also sometimes try mixing and matching by putting one stack inside the other and hiding the navigation bar, but I would say that’s an anti-pattern and can cause issues with transitions and/or items going into the wrong bar)

In terms of how to accomplish this when mixing frameworks: if you use a UINavigationController and want to push a view written in SwiftUI, you can push a UIHostingController onto the nav controller that contains the SwiftUI view. If you use a NavigationStack and want to push a UIViewController, you can push a SwiftUI view that contains a UIViewControllerRepresentable.

Regardless of which framework you pick, I’d structure your navigation data model in a way that is state-driven and framework agnostic. This gives the maximum flexibility and helps with things like testing

It’s fine to use both NavigationStack and UINavigationController in the same app, though, provided they're for separate stacks - one example would be your root-level navigation is in UIKit with a UINavigationController, and when you’re building a new flow, you present a SwiftUI sheet with a NavigationStack for navigation within the sheet

Accepted Answer

This is one of the trickiest parts of incremental SwiftUI adoption. The core issue is that SwiftUI's NavigationStack and UIKit's UINavigationController each want to own the navigation stack — and they fight when nested.

The best practice that holds up: pick one framework to own navigation at each level, never both in the same stack.

If your app's shell is UIKit, keep UINavigationController as the owner and host SwiftUI screens in UIHostingController as pushed view controllers. Drive pushes from UIKit — don't put a NavigationStack inside a hosted SwiftUI view that's already inside a UINavigationController. For SwiftUI screens that need to push, use a coordinator pattern: the SwiftUI view calls back to the UIKit coordinator, which performs the push.

If a section is fully SwiftUI, wrap that whole section in a single UIHostingController and let NavigationStack own everything inside it — don't push individual SwiftUI screens via UIKit within that section.

The anti-pattern that causes the "bad fast" behavior is alternating: UIKit pushes a SwiftUI view, which has its own NavigationStack, which pushes another view that calls back to UIKit. Pick the owner per section and keep handoffs at clear boundaries.

For state, use a shared coordinator/router object (observable) that both worlds read from, rather than letting each framework track its own path.

— Divya Ravi, Senior iOS Engineer

Thanks for the question!

My recommendation would be to not intermix SwiftUI and UIKit navigation within the same ‘stack’ (NavigationStack in SwiftUI, UINavigationController in UIKit) - having the 2 frameworks both trying to mutate the same stack can run into issues. (Apps also sometimes try mixing and matching by putting one stack inside the other and hiding the navigation bar, but I would say that’s an anti-pattern and can cause issues with transitions and/or items going into the wrong bar)

In terms of how to accomplish this when mixing frameworks: if you use a UINavigationController and want to push a view written in SwiftUI, you can push a UIHostingController onto the nav controller that contains the SwiftUI view. If you use a NavigationStack and want to push a UIViewController, you can push a SwiftUI view that contains a UIViewControllerRepresentable.

Regardless of which framework you pick, I’d structure your navigation data model in a way that is state-driven and framework agnostic. This gives the maximum flexibility and helps with things like testing

It’s fine to use both NavigationStack and UINavigationController in the same app, though, provided they're for separate stacks - one example would be your root-level navigation is in UIKit with a UINavigationController, and when you’re building a new flow, you present a SwiftUI sheet with a NavigationStack for navigation within the sheet

Intermixing Navigation with SwiftUI and UIKit
 
 
Q