Sep 19, 2022 iOS

Working with SwiftUI NavigationView and NavigationStack SwiftUI

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:

  1. 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
}
  1. 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.

  1. 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:

  1. 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.

  1. 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.

  1. 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.

  1. 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.

Index