SwiftUI navigation bars on iOS 26: style the system bar, don’t hide it
Hiding the navigation bar to draw your own has always cost you swipe to go back. On iOS 26 both swipe gestures stop working, and the usual delegate workaround never even gets asked. Here's what the system bar can do instead, and where a custom bar still makes sense.
In 2023 I wrote up a custom navigation bar in SwiftUI. On iOS 26, most screens are better off without one: keep the system navigation bar on an ordinary pushed screen and style it. Hide it and draw your own only where swiping back doesn’t matter, like sheets, full-screen covers and the root of a stack.
Styling the system bar
The system bar takes a custom title view, your own buttons, a background colour and a colour scheme:
struct EditorScreen: View { var body: some View { List(1...40, id: \.self) { row in Text("Row \(row)") } .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .principal) { Text("Title") .font(.system(.headline, design: .rounded)) .foregroundStyle(.white) } ToolbarItem(placement: .topBarTrailing) { Button("Save") {} .tint(.white) } .glassHiddenIfAvailable() } .toolbarBackground(.black, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar) .toolbarColorScheme(.dark, for: .navigationBar) } }
On iOS 26, Liquid Glass puts each toolbar button in a glass capsule. .sharedBackgroundVisibility(.hidden) on a ToolbarItem takes it off. That modifier is iOS 26 only, so if you still support older versions, wrap it:
extension ToolbarContent { @ToolbarContentBuilder func glassHiddenIfAvailable() -> some ToolbarContent { if #available(iOS 26.0, *) { sharedBackgroundVisibility(.hidden) } else { self } } }
That compiles against an iOS 17 deployment target and does nothing on iOS 18.
iOS 26 also adds .navigationSubtitle(_:). On older versions, a VStack of two Text views in the .principal item does the same job.

Check the status bar if you use a dark bar like this. With the black background, the iOS 26.5 simulator doesn’t show the status bar at all, while iOS 18.5 draws it in white, as in the first screenshot above. Try a dark bar on iOS 26 before you ship one.
Both examples and the helper are in one file you can download from GitHub: StyledSystemNavigationBar.swift
What stays system-drawn is the back button, and that is usually the point where people hide the bar to draw their own.
What hiding the bar costs on iOS 26
iOS 26 has two ways to swipe back: the old swipe from the left edge, and a new swipe from anywhere in the content. On a pushed screen with the system bar, a horizontal drag across the content pops the screen. Add .toolbar(.hidden, for: .navigationBar) and the same drag does nothing.
The Back button you draw yourself still works, because it calls dismiss(). Only the gesture is gone.
Why the delegate workaround doesn’t help
The usual fix is to give interactivePopGestureRecognizer your own delegate that allows the swipe whenever there is something to pop. On iOS 26 the navigation controller has two such recognizers, interactivePopGestureRecognizer and the new interactiveContentPopGestureRecognizer, and both belong to a private UIKit object, _UINavigationInteractiveTransition.
A proxy delegate in front of both shows why. It answers gestureRecognizerShouldBegin(_:) itself, forwards every other delegate call to UIKit’s original, and logs each call. With the bar hidden, a drag across the content produces this and nothing else:
installed on _UIParallaxTransitionPanGestureRecognizer original=_UINavigationInteractiveTransition installed on _UIParallaxTransitionPanGestureRecognizer original=_UINavigationInteractiveTransition forward _gestureRecognizer:shouldReceiveEvent: forward _gestureRecognizer:shouldReceiveEvent:
UIKit asks a private method, _gestureRecognizer:shouldReceiveEvent:, first. Its own delegate turns the event away, and gestureRecognizerShouldBegin(_:) is never called, so your delegate never gets a say. Getting past that check would mean overriding a private selector, which isn’t something to ship.
Where a custom bar still fits
A working Back button with no swipe is fine when:
- the screen is a sheet or a full-screen cover, which is dismissed rather than swiped back
- it’s the root of a stack, with nothing to go back to
- it’s a step in a flow where an accidental swipe back would be a problem
For those, the component from the custom navigation bar post is a modifier that pins its bar to the top safe area, keeps the title centred with a custom Layout, and gives you a Back button that calls dismiss().