David Yang

Tips and posts for iOS developers from an iOS developer.

Working with SwiftUI, a common task in customizing your app’s design is to build a custom back button for your navigation.

The ‘not so good’ way

In order to do that, the first thing you’ll do is probably go to Stackoverflow and implement this kind of solution.

struct SampleDetails: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var btnBack : some View { Button(action: {
        self.presentationMode.wrappedValue.dismiss()
        }) {
            HStack {
            Image("ic_back") // set image here
                .aspectRatio(contentMode: .fit)
                .foregroundColor(.white)
                Text("Go back")
            }
        }
    }
    
    var body: some View {
            List {
                Text("sample code")
        }
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: btnBack)
    }
}

Source: Stackoverflow

Although it has lot of upvotes, I’m not a big fan for a few reasons:

  • it’s overriding the leading navigation item with a fake back button that does exactly what the original back button was doing
  • all of the system’s animations that came for free with the navigation bar are now lost
  • the swipe gesture becomes unavailable and restoring it will require more hacky code
  • you will have to use the environment’s presentationMode to go back
  • you will have to override your leading navigation item in every screen of your app

The better way

In my opinion, the best way to solve this is to rely on UIKit’s appearance API.

I’m still using an AppDelegate in my SwiftUI project for various reasons (3rd-party SDK setup, push notification handling, etc). So here is what I’m adding in my application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) method:

UINavigationBar.appearance().backIndicatorImage = UIImage(systemName: "arrow.left")
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(systemName: "arrow.left")
UINavigationBar.appearance().tintColor = .black
UIBarButtonItem.appearance().setTitleTextAttributes([
    NSAttributedString.Key.foregroundColor: .black
], for: .normal)

Conclusion

The less we write, the better it is. Especially for behaviors already existing and it will always avoid unexpected behaviors.

With that in mind, it’s still fair to rely on UIKit, especially for features that are still not directly available with SwiftUI. Apple’s UIAppearance API was specifically designed for customizing the navigation items, works well with SwiftUI and allows us to keep benefiting from all the default behaviors and animations that came for free with the system’s navigation.