Building navigation hierarchies in SwiftUI

by @ralfebert · updated September 24, 2021
Xcode 13 & iOS 15
Advanced iOS Developers

When building apps with complex navigation flows in SwiftUI, I sometimes miss the UINavigationController from UIKit where you have a stack that can be programmatically controlled so views can be pushed and popped as needed.

The NavigationView was quite limited in early SwiftUI versions, where building multi-level navigation hierarchies and controlling navigation programmatically wouldn't work consistently. Fortunately this has somewhat improved, so it is now possible to build navigation hierarchies in pure SwiftUI.

So let's take a look what NavigationView and NavigationLink can do.

The idea is to connect the views with NavigationLinks and to create a implicit navigation hierarchy using such links. Activation can be controlled programmatically by using a binding to an isActive value that can be set programmatically to activate and deactivate the link as needed:

struct ContentView: View {
    @State var isActive = false

    var body: some View {
        NavigationLink(
            isActive: $isActive,
            destination: {
                Color.blue
            },
            label: {
                Text("Go to a blue view!")
            }
        )
    }
}

Another way is to use the initializer NavigationLink(tag:selection:destination:label) which will activate the NavigationLink when the tag value matches the selection value:

enum CardSuit: String, CaseIterable {
    case diamonds
    case clubs
    case hearts
    case spades
}

struct ContentView: View {
    @State var activeSuit: CardSuit?

    var body: some View {
        List(CardSuit.allCases, id: \.self) { suit in
            NavigationLink(
                tag: suit,
selection: $activeSuit,
                destination: {
                    Text(suit.rawValue)
                },
                label: {
                    Text(suit.rawValue)
                }
            )
        }
    }
}

This can be combined to build navigation hierarchies that can be controlled programmatically by changing state that then will activate / deactivate NavigationLinks as needed.

Here is an example where you can select a card suit on the first level of navigation and then can go one level deeper to show detail information about this card suit. It's also possible to go back one or two levels:

↗ Example code

There was one detail to make this work: setting navigationViewStyle(.stack) – without this, the navigation view does not seem to allow multi-level navigation:

NavigationView {
    ContentView()
}
.navigationViewStyle(.stack)

Limitations

Programmatic navigation seems to be somewhat limited: I couldn't get to work a Button on the first screen that pushes two levels on the navigation stack by setting both model properties / that jumps directly to the card suit detail.

These packages might be an alternative:

↗ swiftui-navigation-stack
↗ swift-composable-navigator