Cheatsheet for SwiftUI Navigation
How navigation works in SwiftUI often proves to be confusing for new folks. In spite of the apparent attempt by Apple engineers to make app development easier for developers, navigation is one area that is confusing, counterintuitive, and difficult to handle in SwiftUI.
In this blog post, I am going to list 3 common ways for navigating around SwiftUI screens with a cheat sheet to quickly inject the navigation code into your app.
- Navigation with a navigation view (Push or pop screen)
- Presenting a partial modal screen (Partial)
- Presenting a full modal screen (Full)
This cheat sheet will be useful for common navigation references and any time you want to write code for specific navigation.
tl;dr;
Even if you don't have enough time to go through the full article, here's the quick cheat sheet for SwiftUI navigation APIs.
The code is provided with a placeholder text so that you can simply copy and paste these snippets into your code replacing placeholders,
Push Based Navigation
NavigationView {
NavigationLink(isActive: <#T##Binding<Bool>#>,
destination: <#T##() -> _#>,
label: <#T##() -> _#>
)
}
Partial Modal Presentation
<Any View Type>.sheet(
isPresented: <#T##Binding<Bool>#>,
onDismiss: <#T##(() -> Void)?##(() -> Void)?##() -> Void#>,
content: <#T##() -> View#>
)
Full-screen Modal Presentation
<Any View Type>.fullScreenCover(
isPresented: <#T##Binding<Bool>#>,
onDismiss: <#T##(() -> Void)?##(() -> Void)?##() -> Void#>,
content: <#T##() -> View#>
)
Now that we're past the cheat sheet, let's take a look at each way one-by-one
Push Based Navigation with a Navigation View
This kind of navigation is similar to UINavigationController
-based navigation in the UIKit
system. In order to trigger navigation with NavigationView
which involves a push-based transition to the new screen, you can use the NavigationLink
API with the following parameters.
NavigationView {
NavigationLink(
isActive: <is_active>,
destination: <destination>,
label: <label>
)
}
NavigationView
component, which is essentially a navigation controller of SwiftUI
systemPlease note how NavigationLink
is wrapped into the NavigationView
. Wrapping it into NavigationView
makes it part of the navigation stack and any time navigation link is activated, it pushes the destination screen on the navigation stack.
Now that the API is here, let's take a deep look at its parameters,
isActive
 - Takes the binding variable as input. The link is active/inactive based on thetrue
orfalse
value of this variabledestination
- Refers to a view for the navigation link to present once the link is activated throughisActive
parameterlabel
- A view builder to produce a label describing thedestination
to present once the link is activated. For example, you can have a button that activates the link when the user taps on it.
Let's take a look at an example and working demo of NavigationView
-based navigation in SwiftUI,
Here's a sample code for destination TestScreen
,
struct TestScreen: View {
var body: some View {
Text("Welcome to Test Screen")
}
}
And here's a code for NavigationLink
to navigate to TestScreen
,
struct LandingScreen: View {
@State private var showTestScreen = false
var body: some View {
NavigationView {
//...Some other views
NavigationLink(isActive: $showTestScreen) {
TestScreen()
} label: {
Button("Show Test Screen") {
showTestScreen = true
}
}
//...Some more other views
}
}
Partial Modal Presentation
Another way to perform screen navigation is to use public func sheet<Content>(isPresented....
API to present a new screen in SwiftUI
. It presents a new screen in such a way that it leaves slight gap on the top and can be dismissed by vertically dragging the screen from top to bottom.
In order to present a new screen, you can use sheet
API, which is added in the form of an extension on the View
type.
public func sheet<Content>(isPresented: Binding<Bool>, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping () -> Content) -> some View where Content : View
isPresented
 - Takes the binding variable as an input. Used to control whether the sheet is presented or notonDismiss
- A closure that gets called every time the screen is dismissedcontent
: Represents a screen to present once binding variables passed toisPresented
argument istrue
Now that we have a theoretical understanding of API to present a modal screen, let's write a code to see it in action.
@State private var showTestScreen = false
var body: some View {
Button("Show Test Screen") {
showTestScreen = true
}.sheet(isPresented: $showTestScreen) {
// Things to do when the screen is dismissed
} content: {
// Destination screen
TestScreen()
}
}
Full Modal Presentation
There is one more way to navigate to new screen by presenting it in full-screen format. The fullScreenCover
API defined on View
type can be used here. This API has the following signature,
public func fullScreenCover<Content>(isPresented: Binding<Bool>, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping () -> Content) -> some View
isPresented
- Takes the binding variable to control whether the full-screen sheet is presented or notonDismiss
- A closure that gets called every time the screen is dismissedcontent
- Represents a screen to present once the binding variable passed to  theisPresented
argument is true
Let's take a look at code to present the full screen sheet and related demo,
@State private var showTestScreen = false
var body: some View {
Button("Show Test Screen") {
showTestScreen = true
}.fullScreenCover(isPresented: $showTestScreen) {
// Things to do when the screen is dismissed
} content: {
TestScreen()
}
}
Unlike the previous example involving a partial modal screen which can be dismissed by a vertical drag gesture, there is no automatic way to dismiss the full-screen presented screen.
To fix this, we will pass the showTestScreen
in landing screen as a binding variable to TestScreen
. TestScreen
will take this binding variable as input, and set its value to false when the user taps on the "Close" button on this screen.
Once showTestScreen
is set to false, isPresented
passed to fullScreenCover
API on the previous screen will cease to be true
and full-screen cover will be dismissed.
//TestScreen.swift
struct TestScreen: View {
@Binding var showTestScreen: Bool
var body: some View {
Text("Welcome to Test Screen")
Button("Close") {
showTestScreen = false
}
}
}
//LandingScreen.swift
struct LandingScreen: View {
@State private var showTestScreen = false
var body: some View {
Button("Show Test Screen") {
showTestScreen = true
}.fullScreenCover(isPresented: $showTestScreen) {
//no-op
} content: {
TestScreen(showTestScreen: $showTestScreen)
}
}
}
Now that we set up state/binding variables dance, we can easily dismiss the presented full-screen sheet just by toggling the value of this variable from the TestScreen
Summary
So this was all about handling common navigations in SwiftUI using three basic navigation techniques. I always struggle to find the correct API and syntax for handling these scenarios, so I figured it would be far easier to document them in one place with coding examples and a demo.
Hope you liked this article and thanks for reading it. If you have any comments, questions, or feedback, please reach out to me on Twitter @jayeshkawli.