Navigation View in SwiftUI

DevTechie Inc
Jun 11, 2022

Photo by Mick Haupt on Unsplash

Navigation view is used for presenting a stack of views that represents a visible path in a navigation hierarchy. We use a NavigationView to create a navigation-based app where user can navigate through a collection of views. Users navigate to a destination view by selecting a NavigationLink that we provide.

iOS, WatchOS and TVOS pushes a new view onto the stack, and enable removing items from the stack with platform-specific controls, like a Back button or a swipe gesture. Whereas on iPadOS and macOS, the destination content appears in the next column.

Let’s start with a simple example. We will create a simple view as shown below:

struct NavigationViewExample: View {
    var body: some View {
        VStack { // 1
            Text("DevTechie")
                .font(.largeTitle)
                .foregroundColor(.orange)
        }
    }
}
To add NavigationView, we will simply nest VStack (marked with 1 in code above ) inside the NavigationView as shown below:

struct NavigationViewExample: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("DevTechie")
                    .font(.largeTitle)
                    .foregroundColor(.orange)
            }
        }
    }
}
You will not notice much change in the view, until you add a navigationTitle so let’s add that.

struct NavigationViewExample: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("DevTechie")
                    .font(.largeTitle)
                    .foregroundColor(.orange)
            } // end of VStack
            .navigationTitle("Hello DevTechie")
        }// end of Navigation View
    }
}
Notice in the code above sets navigation title inside the NavigationView itself, rather then setting the modifier on NavigationView.

DisplayMode
NavigationView supports two types of display mode.

Large : displays large navigation bar and title

struct NavigationViewExample: View {
    var body: some View {
        NavigationView {
            VStack {
                List(0...30, id: \.self) { item in 
                    Text("Item \(item)")
                }
            } // end of VStack
            .navigationTitle("Hello DevTechie")
            .navigationBarTitleDisplayMode(.large)
        }// end of Navigation View
    }
}
Inline: displays inline navigation bar and title at the top center

struct NavigationViewExample: View {
    var body: some View {
        NavigationView {
            VStack {
                List(0...30, id: \.self) { item in 
                    Text("Item \(item)")
                }
            } // end of VStack
            .navigationTitle("Hello DevTechie")
            .navigationBarTitleDisplayMode(.inline)
        }// end of Navigation View
    }
}
Hide Navigation Bar
You can hide navigation bar using .navigationBarHidden modifier and pass bool value to hide/show navigation bar

struct NavigationViewExample: View {
    var body: some View {
        NavigationView {
            VStack {
                List(0...30, id: \.self) { item in 
                    Text("Item \(item)")
                }
            } // end of VStack
            .navigationTitle("Hello DevTechie")
            .navigationBarTitleDisplayMode(.large)
            .navigationBarHidden(true)
        }// end of Navigation View
    }
}
Pushing Another View in Navigation Stack
Using NavigationLink view you can push another view into navigation stack as shown below:

struct NavigationViewExample: View {
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink("Check out DevTechie.com") { 
                    Text("https://www.devtechie.com")
                }
            } // end of VStack
            .navigationTitle("Hello DevTechie")
            .navigationBarTitleDisplayMode(.large)
        }// end of Navigation View
    }
}
Notice that we are using simple Text view as the destination for NavigationLink. You can replace this with a custom view as shown below:

struct NavigationViewExample: View {
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink("Check out another view") {
                    AnotherView()
                }
            } // end of VStack
            .navigationTitle("Hello DevTechie")
            .navigationBarTitleDisplayMode(.large)
        }// end of Navigation View
    }
}
This will navigate to another view which is:

struct AnotherView: View {
    var body: some View {
        Text("This is another view")
    }
}
Notice the back button that we get for free just by using NavigationLink inside NavigationView.

Hide NavigationBar Back Button
You can also hide back button for navigation bar using navigationBarBackButtonHidden(Bool) modifier. For this we will update our AnotherView example with following code:

struct AnotherView: View {
    var body: some View {
        Text("This is another view")
            .navigationBarBackButtonHidden(true)
    }
}
But now our users will be stuck on second screen 🤔 Let’s create our own back button. We will use Environmentproperty wrapper to dismiss our view. We will also use handy toolbar modifier and add a button at our navigation view’s leading side.

struct AnotherView: View {
    @Environment(\.presentationMode) var presentationMode
    var body: some View {
        Text("This is another view")
            .navigationBarBackButtonHidden(true)
            .toolbar { 
                ToolbarItem(placement: .navigationBarLeading) { 
                    Button("Get Otta Here...") {
                        presentationMode.wrappedValue.dismiss()
                    }
                }
            }
    }
}
Buttons on NavigationBar
As we saw earlier, with the help of toolbar, we can add buttons to NavigationBar. Let’s add a few more buttons to fill our navigation bar. We will add button similar to twitter’s tweet screen:

struct AnotherView: View {
    @Environment(\.presentationMode) var presentationMode
    var body: some View {
        Text("This is another view")
            .navigationBarBackButtonHidden(true)
            .toolbar { 
                ToolbarItemGroup(placement: .navigationBarTrailing) { 
                    Button(action: {}) {
                        Image(systemName: "pencil")
                    }
                    .foregroundColor(.primary)
                    
                    Button(action: {}) {
                        Text("Tweet")
                            .bold()
                            .padding(.horizontal)
                            .padding(.vertical, 5)
                            .background(Capsule().fill(.blue.opacity(0.8)))
                    }
                    .foregroundColor(.primary)
                }                            
                ToolbarItemGroup(placement: .navigationBarLeading) { 
                    Button(action: {
                        presentationMode.wrappedValue.dismiss()
                    }) {
                        Image(systemName: "xmark")
                    }
                    .foregroundColor(.primary)
                }
            }
    }
}
NavigationBar Appearance
NavigationBar’s appearance has been a topic of interest since the first release of SwftUI yet its still not available out of the box in SwiftUI. So we will fallback to the UIKit based approach and see how it works first then we will write our own modifier to handle NavigationBar appearance.

In order to customize NavigationBar’s tint color and background color, we can use UINavigationBar’s appearance object and make our customizations available there.

struct NavigationViewExample: View {
    init() {
        let appearance = UINavigationBarAppearance()
        appearance.titleTextAttributes = [.foregroundColor: UIColor.systemOrange]
        appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.systemOrange]
        appearance.backgroundColor = UIColor.white
        appearance.shadowColor = UIColor.systemGray
        UINavigationBar.appearance().standardAppearance = appearance
        UINavigationBar.appearance().compactAppearance = appearance
        UINavigationBar.appearance().scrollEdgeAppearance = appearance
        UINavigationBar.appearance().tintColor = UIColor.systemOrange
    }
    var body: some View {
        NavigationView {
            List(1...30, id: \.self) { item in 
                Text("item \(item)")
            }
            .navigationTitle("DevTechie")
        }
    }
}
This looks great but it only works for a single view. What if I need this for every single view in my app well, in that case we will need a view modifier for nav appearance, so let’s create one.

struct NavAppearance: ViewModifier {
    init(backgroundColor: UIColor, foregroundColor: UIColor, shadowColor: UIColor, hideSeparator: Bool, tintColor: UIColor) {
        let appearance = UINavigationBarAppearance()
        appearance.titleTextAttributes = [.foregroundColor: foregroundColor]
        appearance.largeTitleTextAttributes = [.foregroundColor: foregroundColor]
        appearance.backgroundColor = backgroundColor
        appearance.shadowColor = shadowColor
        if hideSeparator {
            appearance.shadowColor = .clear
        }
        UINavigationBar.appearance().standardAppearance = appearance
        UINavigationBar.appearance().compactAppearance = appearance
        UINavigationBar.appearance().scrollEdgeAppearance = appearance
        UINavigationBar.appearance().tintColor = tintColor
    }
    
    func body(content: Content) -> some View {
        content
    }
}
As you can see that we have moved all of our init code for setting navigation bar appearance object into this new modifier. We have also added option so hide separator under the navigation bar.

Next we will add this modifier to view as an extension so we can call this modifier the same way as we call all the other modifiers in SwiftUI.

extension View {
    func navAppearance(backgroundColor: UIColor, foregroundColor: UIColor, shadowColor: UIColor, hideSeparator: Bool, tintColor: UIColor) -> some View {
        self.modifier(NavAppearance(backgroundColor: backgroundColor, foregroundColor: foregroundColor, shadowColor: shadowColor, hideSeparator: hideSeparator, tintColor: tintColor))
    }
}
Now is the time to use this newly created modifier,

struct NavigationViewExample: View {
    var body: some View {
        NavigationView {
            List(1...30, id: \.self) { item in 
                Text("item \(item)")
            }
            .navigationTitle("DevTechie")
            .navAppearance(backgroundColor: UIColor.orange, foregroundColor: UIColor.white, shadowColor: UIColor.green, hideSeparator: true, tintColor: UIColor.white)
        }
    }
}
With that we have reached the end of this article. Thank you once again for reading. Don’t forget to subscribe to our weekly newsletter at