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.