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 Audrey Tam.

Leave a rating/review
Download materials
Save for later
Share
Note: This tutorial assumes you’re comfortable with using Xcode to develop iOS apps. You need Xcode 11. To see the SwiftUI preview, you need macOS 10.15. 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 PublicArt-Starter folder. You’ll build a master-detail app using the Artwork.swift and MapView.swift files already included in this project.

SwiftUI Basics in a Nutshell

SwiftUI lets you ignore Interface Builder and storyboards without having to write detailed 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 don’t need a view controller either. And live preview means you rarely need to launch the simulator.

Note: Check out SwiftUI: Getting Started to learn more about the mechanics of developing a single-view SwiftUI app in Xcode.

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 there’s a change to data they depend on. 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! So if you’re already using one of the reactive programming frameworks, you’ll probably 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 basic building blocks for your custom views.

Open ContentView.swift, and make sure 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 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 like 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. And 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 you display them in a List view, which iterates over the array, displaying whatever you specify for each item. And the result looks just like a UITableView!

Make sure your canvas is open, then refresh the preview (click Resume or press Option-Command-P):

And there’s your list, just 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 allowed, as long as the item’s type conforms to the Hashable protocol, which all the built-in types do.

Now 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 Preview

The Live Preview button is the “play” button near the lower right corner of the canvas device. It runs the view in the canvas, but the ordinary live preview won’t stop at the breakpoint. Right-click or Control-click the Live Preview button, then select Debug Preview from the menu.

The first time you run Debug Preview, it will take a while to load everything. Eventually, execution stops at your breakpoint, and the Variables View displays discipline:

Click the Continue program execution button: Now discipline = "mural".

Click Continue again to see discipline = "plaque".

Now, the next time you click the Continue button, what do you think will happen? It’s “statue” again! Surprised? Is this the fourth list item?

Well, click Continue twice more to see “mural” and “plaque” again. Then one final Continue displays the list of four items. So no, execution doesn’t stop for the fourth list item.

What you’ve just seen is: execution visited each of the three unique items twice; “statue” appeared only once on each run-through. So List does see only three unique items. This isn’t a problem for this simple list of strings, but you’ll soon see an example of a non-unique id problem.

You’ll also learn a better way to handle the id parameter. But first, you’ll see how easy it is to navigate to a detail view.

Click the Live Preview button to stop it, and remove the breakpoint.