Navigation Link in SwiftUI

DevTechie Inc
Jan 29, 2023

NavigationLink is a view which controls a navigation presentation. NavigationLink was introduced in iOS 13. Starting iOS 16, there is a slight difference in navigation presentation with the introduction of NavigationStack and NavigationSplitView.

In this article, we will look at NavigationLink for navigation presentation supported from iOS 13 to iOS 15 and navigation presentation for iOS 16+.

Navigation Presentation in iOS 13 to iOS 15(prior iOS 16)
NavigationLink is a view which commands the enclosing NavigationView to push another view into the navigation stack. NavigationLink takes destination and label as parameter for one of its initializer.

destination: is a ViewBuilder which provides the view that will be pushed into the NavigationView.

label: is the view which can be tapped to trigger the navigation command.

struct DevTechieNavigationLinkExample: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("Hello, DevTechie")
                    .font(.largeTitle)
                
                NavigationLink(destination: Text("You reached here via NaviagtionLink")) {
                    HStack {
                        Image(systemName: "computermouse")
                        Text("Click Me!")
                    }
                }
                .padding()
            }
        }
    }
}
Note that we are using a Text view as destination for our example but SwiftUI presents entire view along with back button to complete the experience.
We can build entire experience inside the ViewBuilder. Here is an example but you get the point.

struct DevTechieNavigationLinkExample: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("Hello, DevTechie")
                    .font(.largeTitle)
                
                NavigationLink {
                    VStack {
                        Text("Hello DevTechie!")
                            .font(.largeTitle)
                            .foregroundColor(.white)
                            .padding()
                            .background(Color.orange, in: RoundedRectangle(cornerRadius: 20))
                        
                        Text("Welcome!")
                            .font(.title)
                        Text("Let's learn and grow together!")
                    }
                } label: {
                    HStack {
                        Image(systemName: "computermouse")
                        Text("Click Me!")
                    }
                }
                .padding()
            }
        }
    }
}
Alternatively, we can pass a view to the destination parameter, which will be our navigation destination view.

struct DevTechieNavigationLinkExample: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("Hello, DevTechie")
                    .font(.largeTitle)
                
                NavigationLink(destination: NavDestinationView(), label: {
                    HStack {
                        Image(systemName: "computermouse")
                        Text("Click Me!")
                    }
                })
                .padding()
            }
        }
    }
}
When view is pushed into the navigation stack, setting navigationTitle is as easy as adding as a modifier. We don’t need another NavigationView to set navigation title at the destination view.

Let’s update our example to include navigation title for both main view and destination view.

struct DevTechieNavigationLinkExample: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("Hello, DevTechie")
                    .font(.largeTitle)
                
                NavigationLink(destination: NavDestinationView(), label: {
                    HStack {
                        Image(systemName: "computermouse")
                        Text("Click Me!")
                    }
                })
                .padding()
            }
            .navigationTitle("DevTechie.com")
        }
    }
}struct NavDestinationView: View {
    var body: some View {
        VStack {
            Text("Hello DevTechie!")
                .font(.largeTitle)
                .foregroundColor(.white)
                .padding()
                .background(Color.orange, in: RoundedRectangle(cornerRadius: 20))
            
            Text("Welcome!")
                .font(.title)
            Text("Let's learn and grow together!")
        }
        .navigationTitle("NavDestination View")
    }
}
Notice that the NavDestinationView’s body doesn’t contain NavigationView.
Programmatic navigation is possible with the help of another overload which takes isActive binding value as a parameter. IsActive is a boolean binding that if toggled to true, triggers the navigation.

We will replace NavigationLink to the overload which takes isActive param and add a button which will trigger programmatic navigation.

struct DevTechieNavigationLinkExample: View {
    @State private var triggerNavigation = false
    
    var body: some View {
        NavigationView {
            VStack {
                Text("Hello, DevTechie")
                    .font(.largeTitle)
                
                
                NavigationLink("Click Me!", destination: NavDestinationView(), isActive: $triggerNavigation)
                .padding()
                
                Button("Click me to navigate") {
                    triggerNavigation.toggle()
                }
                .buttonStyle(.borderedProminent)
            }
            .navigationTitle("DevTechie.com")
        }
    }
}
Note: Both navigation link and button will trigger the navigation push command. triggerNavigation parameter resets to false value when back button is tapped on the detail view.
NavigationLink provides a value binding overload as well. NavigationLink takes selection as a parameter which is a binding value and when it matches the tag parameter, linked view is pushed into the navigation stack.

We will add three NavigationLinks and three Buttons to trigger the navigation, link corresponding to the button will issue the navigation command.

struct DevTechieNavigationLinkExample: View {
    @State private var selected: Int? = nil
    
    var body: some View {
        NavigationView {
            VStack {
                Text("Hello, DevTechie")
                    .font(.largeTitle)
                
                
                VStack {
                    NavigationLink("Click View 1!", destination: NavDestinationView(viewName: "View one"), tag: 1, selection: $selected)
                    
                    NavigationLink("Click View 2!", destination: NavDestinationView(viewName: "View two"), tag: 2, selection: $selected)
                    
                    NavigationLink("Click View 3!", destination: NavDestinationView(viewName: "View three"), tag: 3, selection: $selected)
                }
                .padding()
                
                HStack {
                    Button("View 1") {
                        selected = 1
                    }
                    
                    Button("View 2") {
                        selected = 2
                    }
                    
                    Button("View 3") {
                        selected = 3
                    }
                }
            }
            .navigationTitle("DevTechie.com")
        }
    }
}struct NavDestinationView: View {
    var viewName: String
    
    var body: some View {
        VStack {
            Text(viewName)
            Text("Hello DevTechie!")
                .font(.largeTitle)
                .foregroundColor(.white)
                .padding()
                .background(Color.orange, in: RoundedRectangle(cornerRadius: 20))
            
            Text("Welcome to the \(viewName)!")
                .font(.title)
            Text("Let's learn and grow together!")
        }
        .navigationTitle("NavDestination View")
    }
}
NavigationLink label customization is possible with the label view but buttonStyle modifier can also be used to apply button styles.

struct DevTechieNavigationLinkExample: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("Hello, DevTechie")
                    .font(.largeTitle)
                
                NavigationLink(destination: NavDestinationView(viewName: "Detail View"), label: {
                    HStack {
                        Image(systemName: "computermouse")
                        Text("Click Me!")
                    }
                })
                .buttonStyle(.borderedProminent)
                .padding()
            }
        }
    }
}
If the navigation bar is hidden, we can use dismiss environment variable to dismiss the detail view.

Let’s hide navigation bar from detail view and add Environment variable. Starting iOS 15, new environment variable called dismiss has been introduced but if you are supporting version prior that, you will have to use presentationModeenvironment variable.

Prior iOS 15:

struct NavDestinationView: View {
    
    @Environment(\.presentationMode) var presentationMode
    
    var viewName: String
    
    var body: some View {
        VStack {
            Text(viewName)
            Text("Hello DevTechie!")
                .font(.largeTitle)
                .foregroundColor(.white)
                .padding()
                .background(Color.orange, in: RoundedRectangle(cornerRadius: 20))
            
            Text("Welcome to the \(viewName)!")
                .font(.title)
            Text("Let's learn and grow together!")
            
            Button("Dismiss") {
                presentationMode.wrappedValue.dismiss()
            }
            .buttonStyle(.borderedProminent)
            .tint(.red)
        }
        .navigationBarHidden(true)
        .navigationTitle("NavDestination View")
    }
}
iOS 15 and above:

struct NavDestinationView: View {
    
    @Environment(\.dismiss) var dismiss
    
    var viewName: String
    
    var body: some View {
        VStack {
            Text(viewName)
            Text("Hello DevTechie!")
                .font(.largeTitle)
                .foregroundColor(.white)
                .padding()
                .background(Color.orange, in: RoundedRectangle(cornerRadius: 20))
            
            Text("Welcome to the \(viewName)!")
                .font(.title)
            Text("Let's learn and grow together!")
            
            Button("Dismiss") {
                dismiss()
            }
            .buttonStyle(.borderedProminent)
            .tint(.red)
        }
        .navigationBarHidden(true)
        .navigationTitle("NavDestination View")
    }
}
Navigation Presentation in iOS 16 and above
With iOS 16 came NavigationStack and to create a presentation link within NavigationStack we use navigationDestination(for:destination:) modifier. This modifier associates a view with a kind of data and then presents value of that data type from a navigationLink.

Starting iOS 16, NavigationLink takes value as a parameter. Value is an optional value to present. When someone taps or clicks the link, SwiftUI stores a copy of the value. Value is received as a closure parameter in navigationDestinationclosure.

struct DevTechieNavigationLinkExample: View {
    var body: some View {
        NavigationStack {
            VStack {
                Text("DevTechie")
                    .font(.largeTitle)
                NavigationLink("Next page", value: "New Detail View")
            }
            .navigationDestination(for: String.self) { val in
                NavDestinationView(viewName: val)
            }
        }
    }
}
navigationDestination simplifies the navigation strategy by facilitating programmatic navigation because now we can manager navigation state by recording the presented data.

Value is not limited to primitive types only but any type conforming to Hashable protocol can be used with NavigationStack.

Let’s create our own type.

struct DevTechieCourse: Identifiable, Hashable {
    let id = UUID()
    let name: String
}extension DevTechieCourse {
    static var exampleData: [DevTechieCourse] {
        return [
            .init(name: "Mastering SwiftUI"),
            .init(name: "Build Disney Plus Clone in SwiftUI"),
            .init(name: "Build Video Player App in SwiftUI"),
            .init(name: "Build Drawing App in SwiftUI")
        ]
    }
}
Next let’s use NavigationStack, NavigationLink and navigationDestination in a List view.

struct DevTechieNavigationLinkExample: View {
    var body: some View {
            NavigationStack {
                List(DevTechieCourse.exampleData) { course in
                    NavigationLink(course.name, value: course)
                }
                .listStyle(.plain)
                .navigationTitle("DevTechie Courses")
                .navigationDestination(for: DevTechieCourse.self) { course in
                    Text(course.name)
                }
            }
        }
}
With that we have reached the end of this article. Thank you once again for reading. If you liked this, don’t forget to 👏 and follow 😍. Also subscribe to our weekly newsletter at https://www.devtechie.com