Matthew's Dev Blog

Custom Back buttons in SwiftUI

tl;dr

  • use ToolbarItem(placement: .principal) to set the title of the screen, to be shown in the navigation bar
  • use navigationTitle() to set the Back button title when a new screen is pushed onto the navigation stack

Standard naming conventions for the "< Back" button

NavigationView (and before SwiftUI, UINavigationController) is the cornerstone of iOS development, for apps with a primary-detail arrangement. It has a load of well-known standard behaviours, one of which is the naming of the "< Back" button - which returns the user to the previous screen in the navigation stack.

The rules for naming the "< Back" button are:

  • by default, the button title is an arrow "<" followed by the navigationTitle of the previous screen
  • ... unless the title is too long to fit, in which case use the word "Back" instead

This default behaviour works well in most cases, but there are occasions when we want the Back button and navigation title to have different names.

Customisation in UIKit

For a UIKit app, the accepted way of customising the Back button is to set UIViewController.navigationItem.backButtonTitle:

func viewDidLoad() {
    super.viewDidLoad()
    self.navigationItem.backButtonTitle = "The previous page"
}

Sometimes, apps will change the ViewController's title in viewDidDisappear... which isn't great:

func viewDidDisappear(animated: Bool) {
	super.viewDidDisappear(animated: animated)
	self.title = "Go baaack"
}

But this post is about SwiftUI, not UIKit.

How not to do it in SwiftUI

There are several blogposts which explain how to do this, which is usually:

  1. use .navigationBarBackButtonHidden() modifier to hide the system Back button
  2. add @Environment(\.presentationMode) var presentation to the View, so it can be dismissed in code
  3. add a replacement back button to the navigation bar which performs self.presentation.wrappedValue.dismiss()

The main problem with this approach is that it removes a lot of the standard Back button behaviour - including the "long press" gesture which shows the titles of all the previous pages in the navigation stack.

Custom Back buttons in SwiftUI

If you're targeting iOS 14 or later, and using .navigationBarTitleDisplayMode(.inline), there's a better way:

var body: some View {
	NavigationView {
		NavigationLink("Press Me", destination: Text("Detail").navigationTitle("Detail View"))
			.navigationBarTitleDisplayMode(.inline)
			// this sets the Back button text when a new screen is pushed
			.navigationTitle("Back to Primary View")
			.toolbar {
				ToolbarItem(placement: .principal) {
					// this sets the screen title in the navigation bar, when the screen is visible
					Text("Primary View")
				}
			}
	}
}

This uses the new .toolbar view modifier in iOS 14. The .principal placement is the key here. Apple's documentation says:

In iOS, iPadOS, and tvOS, the system places the principal item in the center of the navigation bar. This item takes precedent over a title specified through View/navigationTitle.

What does it look like?

I'll be using this technique in a future release of my Nearly Departed app. It currently shows all departures from a UK railway station, and will soon also show details of a single service.

I want to keep the same screen titles ("Departures" and "Service"), but change the name of the Back button so that the "long press" menu shows the actual route names, so the user can accurately go back to a particular screen in the navigation stack.

Example custom Back button titles in Nearly Departed app. The title is different from the screen title, and still has the standard Back button behaviour
Tagged with:

First published 2 November 2021