How to use NavigationView and NavigationLink in SwiftUI | SwiftUI Bootcamp #30
Drilling from a list into a detail screen is the backbone of most iOS apps. This lesson covers NavigationView and NavigationLink — the building blocks for hierarchical navigation — including how to customize the navigation bar and how to go back programmatically.
What You'll Learn
- How
NavigationViewestablishes a navigation stack and why your content must be nested inside it - How
NavigationLinkpushes a new screen onto the stack using a label and a destination - How to add bar items, control the title display mode, and dismiss programmatically using
@Environment(\.presentationMode)
Mental Model
Think of NavigationView as a stack of index cards. The first card is always visible at the bottom. When you tap a NavigationLink, a new card slides in on top — that's the push transition. When you go back (swipe or tap the back button), the top card slides away to reveal the card below. The card stack remembers its entire history; you can keep pushing cards until you reach your destination, then pop them all the way back.
NavigationView is the card holder. NavigationLink is the action of placing a new card on top. .navigationTitle is the label printed at the top of each card.
Detailed Explanation
NavigationView wraps your content and provides the navigation bar, push/pop transitions, and the back button. Any view you place inside NavigationView automatically gets the navigation bar context, which means .navigationTitle, .navigationBarItems, and .navigationBarTitleDisplayMode modifiers become active.
NavigationLink has several forms. The simplest: NavigationLink("Label text", destination: DestinationView()) — the entire label is tappable and pushes DestinationView. You can also use a custom label (any View) and attach the link anywhere in the hierarchy. The destination is evaluated lazily in newer SwiftUI — the view isn't created until the link is actually tapped.
.navigationTitle("Title") sets the navigation bar title. By default it uses the "large" display mode (title below the bar). .navigationBarTitleDisplayMode(.inline) switches to the smaller inline title. .navigationBarHidden(true) hides the bar entirely — useful for immersive screens.
.navigationBarItems(leading:trailing:) is the older API for adding buttons to the bar. It has been deprecated in favor of .toolbar in iOS 14+, but is still widely used in existing codebases and works reliably.
@Environment(\.presentationMode) provides a way to pop a screen programmatically without waiting for the user to tap the back button. Calling presentationMode.wrappedValue.dismiss() pops the view off the navigation stack. In iOS 15+, @Environment(\.dismiss) is the cleaner replacement.
Code Structure
NavigationViewBootcamp.swift contains two screens: NavigationViewBootcamp (the list screen with bar items and a NavigationLink in both the body and the trailing bar item) and MyOtherScreen (the detail screen demonstrating programmatic back navigation and a further push to a third screen). Both screens use commented-out lines to show additional configuration options.
Complete Code
NavigationViewBootcamp.swift
import SwiftUI
struct NavigationViewBootcamp: View {
var body: some View {
NavigationView { // establishes the navigation stack — everything inside gets the nav bar context
ScrollView {
NavigationLink("Hello, world!", // tappable text label
destination: MyOtherScreen()) // pushed when tapped
Text("Hello, World!")
Text("Hello, World!")
Text("Hello, World!")
}
.navigationTitle("All Inboxes") // sets the large title in the nav bar
//.navigationBarTitleDisplayMode(.inline) // switches to smaller inline title
//.navigationBarHidden(true) // hides the nav bar entirely
.navigationBarItems(
leading:
HStack { // leading area: icon buttons on the left side of the bar
Image(systemName: "person.fill")
Image(systemName: "flame.fill")
}
,
trailing:
NavigationLink( // a NavigationLink can live in the bar — not just in the content area
destination: MyOtherScreen(),
label: {
Image(systemName: "gear") // gear icon triggers navigation to MyOtherScreen
})
.accentColor(.red) // tints the link icon red instead of the default blue
)
}
}
}
struct MyOtherScreen: View {
@Environment(\.presentationMode) var presentationMode // access to this screen's position in the nav stack
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
.navigationTitle("Green Screen!") // nav modifiers go on the ZStack or top-level view, not on Color
//.navigationBarHidden(true)
VStack {
Button("BACK BUTTON") {
presentationMode.wrappedValue.dismiss() // programmatically pops this screen off the stack
}
NavigationLink("Click here", destination: Text("3rd screen!")) // pushing a third level
}
}
}
}
struct NavigationViewBootcamp_Previews: PreviewProvider {
static var previews: some View {
NavigationViewBootcamp()
}
}Code Walkthrough
NavigationView { ... }— This is the wrapper. Every screen in the navigation stack inherits the navigation bar context from this singleNavigationView. If you forget to wrap content inNavigationView,.navigationTitleand.navigationBarItemshave no effect.NavigationLink("Hello, world!", destination: MyOtherScreen())— The simplest form of a navigation link. The first argument is the label (rendered as tappable text).destination:is the view to push. The link handles the push animation automatically..navigationTitle("All Inboxes")— This modifier belongs on the content insideNavigationView, not onNavigationViewitself. This is a common source of confusion — the title applies to the current screen, and SwiftUI reads it to populate the bar..navigationBarItems(leading:trailing:)— Adds views to the left and right sides of the navigation bar. Theleading:side shows icons; thetrailing:side shows aNavigationLinkwith a gear icon, demonstrating that bar items can themselves trigger navigation.@Environment(\.presentationMode) var presentationMode— Available to any view in the navigation stack.presentationMode.wrappedValue.dismiss()pops this screen exactly as if the user tapped the system back button — the back swipe gesture still works normally alongside it.NavigationLink("Click here", destination: Text("3rd screen!"))— Demonstrates a third level of navigation from within a detail screen. SwiftUI automatically handles the "back" stack for all levels.
Common Mistakes
Mistake: Putting .navigationTitle on NavigationView instead of the content inside itNavigationView { content }.navigationTitle("X") sets the title on the NavigationView wrapper, which has no effect on the bar. The modifier must be on the content: NavigationView { content.navigationTitle("X") }.
Mistake: Nesting NavigationView inside another NavigationView
Each NavigationView creates its own independent navigation stack. Nesting them creates two overlapping nav bars and broken back button behavior. Only one NavigationView should exist at the root of each navigation hierarchy.
Mistake: Using NavigationView in new iOS 16+ codebases without a reason
iOS 16 introduced NavigationStack and NavigationSplitView as replacements for NavigationView. These offer programmatic navigation, type-safe destinations, and better iPad/Mac support. NavigationView still works but is soft-deprecated — use NavigationStack for new projects targeting iOS 16+.
Key Takeaways
NavigationViewestablishes the navigation context;.navigationTitleand bar modifiers must be applied to the content inside it, not theNavigationViewwrapper itself.NavigationLinkcreates a tappable area that pushes a destination view — it can be placed in the content body or in the navigation bar.@Environment(\.presentationMode)enables programmatic dismissal, complementing the automatic back button without replacing it.
Last updated: June 27, 2026