Why this happens
On iOS 26 the scroll-edge effect behind a navigation bar (the fade/blur at the top of the screen) is applied by SwiftUI based on the scroll position of the scrollable content directly inside the hierarchy. When you embed a paged TabView inside a NavigationStack, SwiftUI no longer sees a single well-behaved scroll view at the top level. The TabView hosts its own internal scroll machinery for the page swipes, and the vertical ScrollView lives one level deeper inside each page.
The result is exactly the symptom described: the fade/blur renders correctly on first appearance, but as soon as the user scrolls, the effect glitches and is replaced by a hard cut at the top edge. The navigation bar’s edge effect can’t track the nested scroll view reliably, so it snaps between states instead of interpolating.
This is a structural problem, not a styling one. TabView is designed to sit at the top of a view hierarchy — typically as the root, with NavigationStack inside each tab. When you invert that order for a paged layout, you’re using TabView as a pager, which it was never designed for. The supported way to build a vertical or horizontal pager in SwiftUI is a ScrollView with .scrollTargetBehavior(.paging), introduced in iOS 17 and stable through iOS 26.
How to fix it
1. Replace the paged TabView with a ScrollView + paging behavior
The cleanest fix keeps the same visual result — full-screen pages you swipe between — while letting SwiftUI track the scroll position correctly for the navigation bar effect:
struct CardsExample: View {
@State private var currentPage = 0
let pages = Array(1...10)
let colors: [Color] = [.red, .green, .blue, .yellow,
.cyan, .mint, .orange, .purple, .indigo]
private let columns = [
GridItem(.flexible(), spacing: 16),
GridItem(.flexible(), spacing: 16),
]
var body: some View {
NavigationStack {
ScrollView {
LazyVStack(spacing: 0) {
ForEach(pages, id: \.self) { month in
VStack(spacing: 0) {
ScrollView(.vertical) {
LazyVGrid(columns: columns, spacing: 16) {
ForEach(colors, id: \.self) { color in
RoundedRectangle(cornerRadius: 15)
.fill(color)
.frame(height: 200)
}
}
.padding(.horizontal)
.padding(.top, 7)
}
.scrollIndicators(.hidden)
.frame(height: 600) // page height
}
.tag(month)
}
}
}
.scrollTargetBehavior(.paging)
.ignoresSafeArea()
.navigationTitle("Title")
.navigationBarTitleDisplayMode(.inline)
}
}
}
With this structure the outer ScrollView is the scroll view SwiftUI tracks for the navigation bar edge effect, and the fade/blur stays consistent while scrolling.
If your pages don’t actually need independent vertical scrolling, simplify further: drop the inner ScrollView and put the grid content directly in each page of the paging ScrollView. Fewer nested scroll views means fewer edge-effect surprises.
2. Move the TabView to the top of the hierarchy
If you’re using TabView as an actual tab bar (not a pager), the rule is: TabView goes on the outside, NavigationStack inside each tab. This is the structure SwiftUI’s navigation bar edge effects are built around:
TabView {
NavigationStack {
CardsPage()
.navigationTitle("Title")
.navigationBarTitleDisplayMode(.inline)
}
.tabItem { Label("Cards", systemImage: "square.grid.2x2") }
NavigationStack {
SettingsPage()
.navigationTitle("Settings")
}
.tabItem { Label("Settings", systemImage: "gear") }
}
In this arrangement the blur/fade behind the navigation bar works as expected on iOS 26, because each tab’s NavigationStack contains a plain scrollable view that SwiftUI can track directly.
3. If you must keep the nested structure, accept the trade-off
There’s no modifier that reliably restores the scroll-edge blur for a paged TabView inside a NavigationStack on iOS 26 — the effect depends on hierarchy structure, not on a toggleable setting. If you can’t restructure, your options are:
- Use a solid or transparent navigation bar style and skip the blur entirely.
- Keep the
TabViewas the root and push the paged content into a detail view viaNavigationStacknavigation, so the pager isn’t directly under the navigation bar.
Restructuring per fix 1 is almost always the better path, since .scrollTargetBehavior(.paging) reproduces TabView‘s page-swipe behavior with full scroll tracking.
How to prevent it
- Never nest a paged
TabViewinside aNavigationStack; useScrollView+.scrollTargetBehavior(.paging)for pagers. - Keep
TabViewat the root of your view hierarchy, withNavigationStackinside each tab. - Avoid stacking multiple nested scroll views when one will do — the scroll-edge effect tracks the outermost scrollable.
- Test scroll-edge effects on a device or simulator running the minimum deployment target (iOS 26+), since the behavior differs from older OS versions.
FAQ
Why does the blur work on first load but break as soon as I scroll?
The navigation bar’s scroll-edge effect initializes based on the view hierarchy, then tries to track scroll offsets during scrolling. With a paged TabView between the NavigationStack and the actual ScrollView, that tracking fails and the effect snaps to a hard cut.
Does .scrollTargetBehavior(.paging) work on iOS 26?
Yes. It was introduced with iOS 17 and remains the supported way to build paged scrolling in SwiftUI, including on iOS 26. It gives you the same swipe-between-pages behavior as a paged TabView while keeping scroll tracking intact.
Can I just apply .toolbarBackground or a blur modifier to fix it?
No. The hard cut comes from SwiftUI losing track of the scroll position, not from a missing background style. Background and blur modifiers change the bar’s appearance but won’t restore the dynamic scroll-edge fade in a broken hierarchy.
Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.
