Post

SwiftUI Custom Navigation Back Button

SwiftUI Custom Navigation Back Button

SwiftUI’s default NavigationStack and NavigationView provide a system back button, but customizing its appearance requires replacing it entirely. Unlike UIKit’s navigationItem.leftBarButtonItem, there’s no direct API to style the default back button. This guide shows how to implement a custom back button with full control over appearance and behavior while preserving the native swipe-to-dismiss gesture users expect.

Solution: Custom Back Button with Toolbar

The cleanest approach is to replace the default back button with a custom ToolbarItem using the .topBarLeading placement:

1
2
3
4
5
6
7
8
9
10
.toolbar {
    ToolbarItem(placement: .topBarLeading) {
        Button {
            // dismiss logic
        } label: {
            // custom icon
        }
    }
}
.navigationBarBackButtonHidden()

Preserving the Swipe-to-Dismiss Gesture

When you hide the navigation bar back button with .navigationBarBackButtonHidden(), SwiftUI also disables the built-in swipe-to-pop gesture (swiping from the left edge). This gesture is valuable for accessibility and user experience, so you’ll want to restore it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extension UINavigationController: @retroactive UIGestureRecognizerDelegate {

    override open func viewDidLoad() {
        super.viewDidLoad()
        self.interactivePopGestureRecognizer?.delegate = self
        self.interactiveContentPopGestureRecognizer?.delegate = self
    }

    public func gestureRecognizerShouldBegin(_: UIGestureRecognizer) -> Bool {
        // Prevent gesture on root view to avoid crashes/freezes
        // The gesture should only work when there are views to pop
        return self.viewControllers.count > 1
    }
}

Important Notes

  • Add the UINavigationController extension once - It only needs to exist in your project, not in every view
  • Swipe gesture won’t trigger custom back button logic - The swipe gesture directly pops the view controller. If you need custom logic on all navigations (including swipe), you can simply add .onDisappear { // custom logic } to your SwiftUI view to execute it.
This post is licensed under CC BY 4.0 by the author.