SwiftUI Tutorial: Navigation
In this tutorial, you’ll use SwiftUI to implement the navigation of a master-detail app. You’ll learn how to implement a navigation stack, a navigation bar button, a context menu and a modal sheet. By Fabrizio Brancati.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
SwiftUI Tutorial: Navigation
30 mins
- Getting Started
- SwiftUI Basics in a Nutshell
- Declarative App Development
- Declaring Views
- Creating a Basic List
- The List id Parameter
- Starting Debug
- Navigating to the Detail View
- Creating a Navigation Link
- Revisiting Honolulu Public Artworks
- Creating Unique id Values With UUID()
- Conforming to Identifiable
- Showing More Detail
- Declaring Data Dependencies
- Guiding Principles
- Tools for Data Flow
- Adding a Navigation Bar Button
- Reacting to Artwork
- Adding a Context Menu
- Bonus Section: Eager Evaluation
- Where to Go From Here?
Also, this tutorial assumes you’re comfortable with using Xcode to develop iOS apps. You need Xcode 14. Some familiarity with UIKit and SwiftUI will be helpful.
Also, this tutorial assumes you’re comfortable with using Xcode to develop iOS apps. You need Xcode 14. Some familiarity with UIKit and SwiftUI will be helpful.
Getting Started
Use the Download Materials button at the top or bottom of this tutorial to download the starter project. Open the PublicArt project in the Starter folder. You’ll build a master-detail app using the Artwork.swift file already included in this project.
SwiftUI Basics in a Nutshell
SwiftUI lets you ignore Interface Builder and storyboards without having to write step-by-step instructions for laying out your UI. You can preview a SwiftUI view side-by-side with its code — a change to one side will update the other side, so they’re always in sync. There aren’t any identifier strings to get wrong. And it’s code, but a lot less than you’d write for UIKit, so it’s easier to understand, edit and debug. What’s not to love?
The canvas preview means you don’t need a storyboard. The subviews keep themselves updated, so you also don’t need a view controller. And live preview means you rarely need to launch the simulator.
SwiftUI doesn’t replace UIKit. Like Swift and Objective-C, you can use both in the same app. At the end of this tutorial, you’ll see how easy it is to use a UIKit view in a SwiftUI app.
Declarative App Development
SwiftUI enables you to do declarative app development: You declare both how you want the views in your UI to look and also what data they depend on. The SwiftUI framework takes care of creating views when they should appear and updating them whenever data they depend on changes. It recomputes the view and all its children, then renders what has changed.
A view’s state depends on its data, so you declare the possible states for your view and how the view appears for each state — how the view reacts to data changes or how data affect the view. Yes, there’s a definite reactive feeling to SwiftUI! If you’re already using one of the reactive programming frameworks, you’ll have an easier time picking up SwiftUI.
Declaring Views
A SwiftUI view is a piece of your UI: You combine small views to build larger views. There are lots of primitive views like Text
and Color
, which you can use as building blocks for your custom views.
Open ContentView.swift, and ensure its canvas is open (Option-Command-Return). Then click the + button or press Command-Shift-L to open the Library:
The first tab lists primitive views for layout and control, plus Layouts, Other Views and Paints. Many of these, especially the control views, are familiar to you as UIKit elements, but some are unique to SwiftUI.
The second tab lists modifiers for layout, effects, text, events and other purposes, including presentation, environment and accessibility. A modifier is a method that creates a new view from the existing view. You can chain modifiers like a pipeline to customize any view.
SwiftUI encourages you to create small reusable views, then customize them with modifiers for the specific context where you use them. Don’t worry. SwiftUI collapses the modified view into an efficient data structure, so you get all this convenience with no visible performance hit.
Creating a Basic List
Start by creating a basic list for the master view of your master-detail app. In a UIKit app, this would be a UITableViewController
.
Edit ContentView
to look like this:
struct ContentView: View {
let disciplines = ["statue", "mural", "plaque"]
var body: some View {
List(disciplines, id: \.self) { discipline in
Text(discipline)
}
}
}
You create a static array of strings and display them in a List
view, which iterates over the array, displaying whatever you specify for each item. And the result looks like a UITableView
!
Ensure your canvas is open, then refresh the preview (click the Resume button or press Option-Command-P):
There’s your list, like you expected to see. How easy was that? No UITableViewDataSource
methods to implement, no UITableViewCell
to configure, and no UITableViewCell
identifier to misspell in tableView(_:cellForRowAt:)
!
The List id
Parameter
The parameters of List
are the array, which is obvious, and id
, which is less obvious. List
expects each item to have an identifier, so it knows how many unique items there are (instead of tableView(_:numberOfRowsInSection:)
). The argument \.self
tells List
that each item is identified by itself. This is OK as long as the item’s type conforms to the Hashable
protocol, which all the built-in types do.
Take a closer look at how id
works: Add another "statue"
to disciplines
:
let disciplines = ["statue", "mural", "plaque", "statue"]
Refresh the preview: all four items appear. But, according to id: \.self
, there are only three unique items. A breakpoint might shed some light.
Add a breakpoint at Text(discipline)
.
Starting Debug
Run the simulator, and the app execution stops at your breakpoint, and the Variables View displays discipline
:
Click the Continue program execution button: Now discipline = "statue"
again.
Click Continue again to see discipline = "mural"
. After tapping on Continue, you see the same value, mural, again. Same happens in the next two clicks on the Continue as well with discipline = "plaque"
. Then one final Continue displays the list of four items. So no — execution doesn’t stop for the fourth list item.
What you’ve seen is: execution visited each of the three unique items twice. So List
does see only three unique items. Later, you’ll learn a better way to handle the id
parameter. But first, you’ll see how easy it is to navigate to a detail view.
Stop the simulator execution and remove the breakpoint.
Navigating to the Detail View
You’ve seen how easy it is to display the master view. It’s about as easy to navigate to the detail view.
First, embed List
in a NavigationStack
, like this:
NavigationStack {
List(disciplines, id: \.self) { discipline in
Text(discipline)
}
.navigationBarTitle("Disciplines")
}
This is like embedding a view controller in a navigation controller: You can now access all the navigation items such as the navigation bar title. Notice .navigationBarTitle
modifies List
, not NavigationStack
. You can declare more than one view in a NavigationStack
, and each can have its own .navigationBarTitle
.
Refresh the preview to see how this looks:
Nice! You get a large title by default. That’s fine for the master list, but you’ll do something different for the detail view’s title.