You need two layers of NavigationStack:
struct ContentView: View {
@State private var fullScreenPath = NavigationPath()
@State private var selectedTab = 0
var body: some View {
// Outer stack — pushes OVER tabs (tabs hidden)
NavigationStack(path: $fullScreenPath) {
TabView(selection: $selectedTab) {
// Inner stack — pushes WITHIN tab (tabs visible)
NavigationStack {
HomeView()
.navigationDestination(for: Product.self) { product in
ProductDetailView(product: product)
}
}
.tabItem { Label("Home", systemImage: "house") }
.tag(0)
}
.navigationDestination(for: FullScreenRoute.self) { route in
CheckoutView() // tabs hidden here
}
}
}
}
The trick: inner NavigationStack (per tab) keeps tabs visible. Outer NavigationStack (wrapping TabView) pushes on top, hiding tabs.
Why not fullScreenCover? Because you'd lose the swipe-back gesture. With the outer NavigationStack, swiping back reveals the tabs smoothly
underneath — exactly the native iOS feel you described.
This is how apps like Instagram work: tap a post → pushes within the tab (tabs visible), start composing → pushes over tabs (tabs hidden),
swipe back → tabs reappear.
