SwiftUI 4 New Navigation API

This is a cross-post: I provide canonical versions of blog posts I have written on different websites. This blog post was written for Swift for Javascript Developers

WWDC22 brings a lot of welcome improvements to SwiftUI and in this blog post I will talk about improvements to the Navigation API. Starting with SwiftUI 4, the NavigationView has been deprecated across the following Platforms:

  • iOS
  • iPadOS
  • MacOS
  • Mac Catalyst
  • tvOS
  • watchOS

Instead, SwiftUI 4 provides a couple of different views depending on your use case.

NavigationStack

A view that displays a root view and enables you to present additional views over the root view.

This view has been designed to be a direct replacement for the NavigationView and by default can be used in the same way just by replacing NavigationView with NavigationStack.

struct ContentView: View {
    var body: some View {
        NavigationStack {
            NavigationLink {
                Text("MyCustomView")
            } label: {
                Text("Navigation Item 1")
            }
            .navigationTitle("Navigation Title")
        }
    }
}

NavigationStack provides us with alternative ways to manage the navigation within our apps allowing us to deep-link and programmatically update our navigation stacks.

struct ContentView: View {
    var todos: [Todo] = [
        Todo(id: UUID(), name: "Watch WWDC22 Videos"),
        Todo(id: UUID(), name: "Write WWDC22 Articles")
    ]
    var body: some View {
        NavigationStack {
            List(todos) { todo in
                NavigationLink(todo.name, value: todo)
            }
            .navigationDestination(for: Todo.self) { todo in
                Text("\(todo.name)")
                    .navigationTitle(todo.name)
            }
        }
    }
}

The NavigationLink now has a new initialiser that binds a value to a NavigationLink. SwiftUI 4 also provides a navigationDestination view modifier that allows us to provide a detail view for the bound value. This decouples the navigation destination from the navigation link.

If you have done any web development you will know every web page is based on a route. With iOS 16, Apple has created a similar model providing us the ablity to progmatically set the route path.

To enable this route based functionality, You can call the NavigationStack initialiser with a binding to an array (stack) of values.

By altering the array, the navigation will update automatically.

struct ContentView: View {
    var todos: [Todo] = [
        Todo(id: UUID(), name: "Watch WWDC Videos"),
        Todo(id: UUID(), name: "Write WWDC Articles")
    ]

    @State var currentPath: [Todo] = []

    var body: some View {
        NavigationStack(path: $currentPath) {
            List {
                ForEach(todos) { todo in
                    NavigationLink(todo.name, value: todo)
                }
            }
            .navigationTitle("Todolist")
            .navigationDestination(for: Todo.self) { todo in
                Text("\(todo.name)")
                    .navigationTitle(todo.name)
            }
        }
        .onOpenURL {
            currentPath.append(
                contentsOf: [
                    Todo(id: UUID(), name: "Deeplink Parent Video"),
                    Todo(id: UUID(), name: "Deeplink Child View")
                ]
            )
        }
    }
}

NavigationSplitView

A view that presents views in two or three columns, where selections in leading columns control presentations in subsequent columns.

What is great with the NavigationSplitView is on smaller screen sizes, it will behave exactly like the NavigationStack which is useful when targetting multiple platforms like iOS and iPadOS.

struct ContentView: View {
    var rooms: [String] = ["Living Room", "Kitchen", "Dining Room", "Bedroom"]
    @State private var room: String?

    var body: some View {
        NavigationSplitView {
            List(rooms, id: \.self, selection: $room) { room in
                Text(room)
            }
        } detail: {
            Text(room ?? "Please Select A Room")
        }
    }
}

Subscribe to my newsletter to keep upto date with blog posts, tutorials and thoughts each week.