SwiftUI NavigationView and NavigationStack SwiftUI : SwiftUI is a declarative data-driven framework that allows us to create complicated user interfaces by describing how data is shown on the screen. From the beginning, the framework’s biggest pain point was navigation. Fortunately, since WWDC 22, things have changed, and SwiftUI now includes the new data-driven Navigation API. We’ll learn how to utilise the new Navigation API to create complicated user flows this week.
SwiftUI Navigation View has been the framework’s Achilles heel since its inception. SwiftUI’s Navigation framework frequently pushed us to revert to utilising the UINavigationController, from not allowing lazy loading of destination views within NavigationLink at first (though this was eventually rectified) to its inability to programmatically browse deep connections.
A new NavigationStack that allows you to push and pop views from a stack, a NavigationPath for managing the routing stack, and a navigationDestination modifier for effectively traversing views programmatically are among the significant new Navigation API enhancements. They deprecated NavigationView in the same upgrade.
To offer a stack of views above a root view, use a navigation stack. Views may be added to the top of the stack by clicking or pressing a NavigationLink, and views can be removed by utilising built-in, platform-appropriate controls such as a Back button or a swipe motion. The stack always displays the most recently added view that has not been deleted and does not permit the removal of the root view.
SwiftUI NavigationView
First and foremost, the old Navigation View has been deprecated, and we should instead utilise the new NavigationStack. Consider the following example.
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello, coder")
}
}
}
Navigation views, you see, allow us to display additional content screens by sliding them in from the right side. Each screen may have its own title, and SwiftUI is responsible for ensuring that title is always visible in the navigation view – you’ll see the old title slide away as the new title animates in.
Navigation View with NavigationTitle
We can add the navigation Title in navigationView as shown in below code
NavigationView {
Text("leadbycode")
.navigationBarTitle("Title")
}
Navigation View are been customizable it can be customizable on the basis of .large , inline and automatic
The title is shown by adding a navigationBarTitleDisplayMode() modification, which gives us three options:
- The .large option displays huge headlines, which are excellent for top-level navigation stack views.
- Small titles are displayed using the.inline option, which is excellent for secondary, tertiary, or subsequent views in your navigation stack.
- The.automatic option is the default, and it uses the previous view.
NavigationView with .large
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello, coder")
.navigationTitle("Popup").navigationBarTitleDisplayMode(.large)
}
}
}
NavigationView with .inline
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello, coder")
.navigationTitle("Popup").navigationBarTitleDisplayMode(.inline)
}
}
}
NavigationView with .automatic
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello, coder")
.navigationTitle("Popup").navigationBarTitleDisplayMode(.automatic)
}
}
}
How to push new screen(View) onto Navigation View
NavigationView is the important control in the swiftUI in the below example we can navigate from first screen to second screen using NavigationLink. using NavigationLink we can push to newscreen . Using the destination we can pass the data to the next screen.
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: Text("New Screen")) {
Text("Hello, coder")
.navigationTitle("Popup").navigationBarTitleDisplayMode(.large)
}
}
}
}
When we have the two screen we can push from one screen to another using the navigationlink as shown below
Use NavigationLink – a button that activates a navigation presentation when hit – to implement the secondview and provide a second view on top of the first view in Navigation View:
When a detail view is displayed, the Navigation View handles the addition of the back button automatically.
Use the navigationBarTitle() modification within your SecondView body to add a title to the second view:
struct SecondView: View {
var body: some View {
Text("Second Screen")
}
}
struct FirstView: View {
var body: some View {
NavigationView {
NavigationLink(destination: SecondView()) {
Text("Show second view")
}
.navigationBarTitle("First view").navigationBarTitleDisplayMode(.large)
}
}
}
SwiftUI’s NavigationView
is a container view that provides a navigation bar and a stack-based navigation interface for presenting views hierarchically. It’s used to implement a navigation flow in your app, allowing users to move back and forth between different screens and levels of content.
To use NavigationView
, you typically start by wrapping your initial view in a NavigationView
instance. Inside the NavigationView
, you add a NavigationLink
for each view you want to navigate to. When a user taps a link, the corresponding view is pushed onto the navigation stack, replacing the current view.
Here’s an example of a simple NavigationView
hierarchy:
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink("First View", destination: FirstView())
NavigationLink("Second View", destination: SecondView())
}
.navigationTitle("Main Menu")
}
}
}
struct FirstView: View {
var body: some View {
Text("This is the first view.")
.navigationTitle("First View")
}
}
struct SecondView: View {
var body: some View {
Text("This is the second view.")
.navigationTitle("Second View")
}
}
In this example, the ContentView
is wrapped in a NavigationView
. Inside the NavigationView
, there are two NavigationLink
instances that navigate to the FirstView
and SecondView
views when tapped. Each view sets its own navigation title using the .navigationTitle
modifier.
When you run this code, you should see a navigation bar at the top of the screen with the title “Main Menu”, and two links that lead to the FirstView
and SecondView
views. When you tap one of the links, the corresponding view slides in from the right, and the navigation bar updates with the new view’s title. You can then use the back button in the navigation bar to go back to the previous view.
In SwiftUI, you can customize the navigation bar title using the navigationBarTitle()
modifier. Here’s an example:
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello, World!")
.navigationBarTitle("My App", displayMode: .inline)
}
}
}
In the above example, we’re using the navigationBarTitle()
modifier to set the title of the navigation bar to “My App”. We’re also specifying the display mode as .inline
, which means the title will be displayed in the same line as the back button.
You can also customize the appearance of the title using the font()
and foregroundColor()
modifiers, like this:
.navigationBarTitle("My App", displayMode: .inline)
.font(.title)
.foregroundColor(.blue)
In this example, we’re setting the font size of the title to .title
and the text color to blue.
If you want to display a custom view in place of the title, you can use the navigationBarTitle(_:displayMode:)
method with a View
instead of a String
. For example:
.navigationBarTitle(
Text("My Custom Title")
.font(.title)
.foregroundColor(.blue),
displayMode: .inline
)
Add buttons to the navigation view
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello, World!")
.navigationBarTitle("My App")
.navigationBarItems(trailing:
HStack {
Button(action: {
// handle button tap
}) {
Image(systemName: "plus")
}
Button(action: {
// handle button tap
}) {
Image(systemName: "gear")
}
}
)
}
}
}
In this example, we have added two buttons to the navigation bar: a “plus” button and a “gear” button. The navigationBarItems
modifier takes a trailing
parameter, which is a view that will be displayed on the trailing edge of the navigation bar (i.e. on the right side for left-to-right languages). We have used an HStack
to display multiple buttons side by side.
Note that the action
parameter of the Button
initializer is a closure that will be called when the button is tapped. You can add your own code here to handle the button tap. In this example, we have just left the closure empty.
SwiftUI Data Passing using NavigationView
In SwiftUI, NavigationView provides a navigation stack for presenting views in a hierarchical manner, and it also provides a built-in way to pass data between views. Here’s an example of how to pass data between views using NavigationView:
- Define your model
First, you need to define a model that holds the data you want to pass between views. For example, you might have a model that looks like this:
struct Person: Identifiable {
var id = UUID()
var name: String
var age: Int
}
- Create the views
Next, you need to create the views that will display and edit the data. For example, you might have a list view and a detail view:
struct ListView: View {
let people = [
Person(name: "Alice", age: 25),
Person(name: "Bob", age: 30),
Person(name: "Charlie", age: 35)
]
var body: some View {
NavigationView {
List(people) { person in
NavigationLink(destination: DetailView(person: person)) {
Text(person.name)
}
}
}
}
}
struct DetailView: View {
let person: Person
var body: some View {
Text("\(person.name) is \(person.age) years old")
}
}
In the list view, we use a NavigationLink to navigate to the detail view when the user taps on a person in the list. The DetailView takes a Person object as a parameter, which we will pass from the list view.
- Pass the data
To pass the data from the list view to the detail view, we simply pass the person object as a parameter when creating the NavigationLink:
NavigationLink(destination: DetailView(person: person)) {
Text(person.name)
}
And in the detail view, we can simply access the person object:
struct DetailView: View {
let person: Person
var body: some View {
Text("\(person.name) is \(person.age) years old")
}
}
And that’s it! With NavigationView and NavigationLink, passing data between views in SwiftUI is easy and intuitive.
SwiftUI NavigationStack
NavigationStack
is not a built-in component in SwiftUI, but it is likely a custom implementation created by someone for their specific use case.
In general, SwiftUI’s navigation view hierarchy is managed by a NavigationView
component and its NavigationLink
and NavigationButton
sub-components. When you use a NavigationLink
or NavigationButton
, SwiftUI automatically pushes a new view onto the navigation stack and presents it to the user when the link is tapped. The user can then use the back button to navigate back to the previous view on the stack.
Here’s an example of using NavigationView
and NavigationLink
in SwiftUI:
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView()) {
Text("Go to Detail View")
}
}
.navigationBarTitle("Main View")
}
}
}
struct DetailView: View {
var body: some View {
VStack {
Text("This is the detail view!")
}
.navigationBarTitle("Detail View")
}
}
In this example, when the user taps “Go to Detail View”, SwiftUI automatically pushes DetailView
onto the navigation stack and presents it to the user. The user can then use the back button in the navigation bar to navigate back to ContentView
.
You can also use the @EnvironmentObject
property wrapper to pass a shared data model between views, allowing you to update data in one view and have those changes reflected in another view. This can be useful for creating more complex navigation flows.
Overall, SwiftUI provides a flexible and powerful system for managing navigation views, and with a bit of experimentation, you can create almost any kind of navigation flow you need.
NavigationSplitView
NavigationSplitView
is not a built-in component in SwiftUI. However, there is a built-in NavigationView
component that can be used to create a navigation hierarchy in a split view interface.
A split view interface displays two views side-by-side on larger devices, such as iPads and Macs. The left-hand view typically contains a list of items, while the right-hand view displays detailed information about the selected item.
Here’s an example of how you can use a NavigationView
to create a split view interface in SwiftUI:
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: DetailView(text: "Item 1")) {
Text("Item 1")
}
NavigationLink(destination: DetailView(text: "Item 2")) {
Text("Item 2")
}
}
Text("Select an item")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
struct DetailView: View {
var text: String
var body: some View {
Text(text)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
In this example, we have a NavigationView
containing a List
on the left-hand side, with two NavigationLink
items. When the user taps one of the links, SwiftUI automatically displays the DetailView
on the right-hand side, showing the selected item’s details.
Note that on smaller devices, such as iPhones, the split view interface is not used, and the views are stacked on top of each other in a regular navigation hierarchy. Also note that this example does not use the split view controller that is typically used on iPads and Macs to manage the split view interface. Instead, it demonstrates how you can use SwiftUI’s built-in NavigationView
component to create a simple split view interface.
Programatic Navigation in SwiftUI
In SwiftUI, programmatic navigation can be achieved by using the NavigationLink view and the NavigationView container.
To perform programmatic navigation, you can use the NavigationLink view with an empty label, and then trigger the navigation programmatically using a @State
variable to control the selection state of the link. Here is an example:
struct ContentView: View {
@State private var isActive = false
var body: some View {
NavigationView {
VStack {
Button("Navigate") {
isActive = true
}
NavigationLink(
destination: DetailView(),
isActive: $isActive,
label: { EmptyView() }
)
}
}
}
}
In the above example, when the “Navigate” button is tapped, the isActive
state variable is set to true, which triggers the NavigationLink to navigate to the DetailView
. The isActive
variable is passed to the isActive
parameter of the NavigationLink to control its selection state.
You can also perform programmatic navigation using the NavigationLink(destination:, tag:, selection:)
initializer. This method allows you to define a tag for each destination, and a selection state variable to control the currently selected tag. Here is an example:
struct ContentView: View {
enum Destination {
case home, detail
}
@State private var destination: Destination? = nil
var body: some View {
NavigationView {
VStack {
Button("Navigate") {
destination = .detail
}
}
}
.navigation(item: $destination) { destination in
switch destination {
case .home:
HomeView()
case .detail:
DetailView()
}
}
}
}
In the above example, the Destination
enum defines the available destinations, and the destination
state variable is used to control the currently selected destination. When the “Navigate” button is tapped, the destination
state variable is set to .detail
, which triggers the navigation to the DetailView
. The navigation(item:, content:)
modifier is used to dynamically set the destination view based on the value of the destination
variable.
Programmatic Navigation in SwiftUI using EnvironmentObject
You can use @EnvironmentObject
property wrapper to pass values between views in SwiftUI using navigation. The @EnvironmentObject
allows you to inject an object into the environment of a view hierarchy, making it available to all the views in the hierarchy.
To pass a value using @EnvironmentObject
, you first need to create an object that conforms to the ObservableObject
protocol. This object will hold the value that you want to pass between views. Here’s an example:
class UserData: ObservableObject {
@Published var name = "John"
}
In this example, we’re creating a simple UserData
object that has a name
property. The @Published
property wrapper is used to automatically publish changes to the name
property, which makes it easy to observe changes in the view.
Next, you can create a view that injects the UserData
object into the environment using the @EnvironmentObject
property wrapper. Here’s an example:
struct ContentView: View {
@EnvironmentObject var userData: UserData
var body: some View {
VStack {
Text("Welcome, \(userData.name)!")
NavigationLink(destination: DetailView()) {
Text("Go to detail view")
}
}
}
}
In this example, we’re using the @EnvironmentObject
property wrapper to inject the UserData
object into the ContentView
. We’re also using a NavigationLink
to navigate to the DetailView
.
To pass the UserData
object to the DetailView
, we can inject it using the @EnvironmentObject
property wrapper again. Here’s an example:
struct DetailView: View {
@EnvironmentObject var userData: UserData
var body: some View {
VStack {
Text("Hello, \(userData.name)!")
Button("Change name") {
userData.name = "Jane"
}
}
}
}
In this example, we’re using the @EnvironmentObject
property wrapper again to inject the UserData
object into the DetailView
. We’re also using a Button
to change the name
property of the UserData
object.
To make this work, you need to make sure to set the userData
object as an environment object in the SceneDelegate
. Here’s an example
let userData = UserData()
let contentView = ContentView()
.environmentObject(userData)
window.rootViewController = UIHostingController(rootView: contentView)
In this example, we’re creating a UserData
object and passing it to the ContentView
using the environmentObject
modifier. This makes the UserData
object available to all the views in the view hierarchy.
In SwiftUI, you can customize the appearance and behavior of the navigation bar using the navigationBarTitle
, navigationBarItems
, and navigationBarBackButtonHidden
modifiers.
Here are some examples of how to use these modifiers to customize the navigation bar:
- Customizing the title:
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello, world!")
.navigationBarTitle("My Title", displayMode: .inline)
}
}
}
In this example, we’re using the navigationBarTitle
modifier to set the title of the navigation bar to “My Title”. We’re also using the displayMode
parameter to specify how the title should be displayed.
- Adding items to the leading and trailing edge of the navigation bar:
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello, world!")
.navigationBarTitle("My Title", displayMode: .inline)
.navigationBarItems(
leading: Button("Settings") {},
trailing: Button("Profile") {}
)
}
}
}
In this example, we’re using the navigationBarItems
modifier to add buttons to the leading and trailing edge of the navigation bar. We’re using the leading
parameter to add a “Settings” button and the trailing
parameter to add a “Profile” button.
- Hiding the back button:
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello, world!")
.navigationBarTitle("My Title", displayMode: .inline)
.navigationBarBackButtonHidden(true)
}
}
}
In this example, we’re using the navigationBarBackButtonHidden
modifier to hide the back button in the navigation bar.
- Customizing the background and text color of the navigation bar:
struct ContentView: View {
init() {
UINavigationBar.appearance().backgroundColor = .red
UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.white]
}
var body: some View {
NavigationView {
Text("Hello, world!")
.navigationBarTitle("My Title", displayMode: .inline)
}
}
}
In this example, we’re using the UINavigationBar
class to customize the background and text color of the navigation bar. We’re using the backgroundColor
property to set the background color to red and the largeTitleTextAttributes
property to set the text color to white. Note that you need to set these properties in the init
method of the view to apply them to all instances of the navigation bar in your app.