SwiftUI: Lists, Navigation, and Detail Views

Making lists and navigating them to detailed views with minor code changes

Thus far in our series on SwiftUI, our demo project consists of a dynamic profile page that’s ready to take in any Person and display their attributes. In this tutorial, we’ll get to actually make a list of folks that we can scroll through using a List and, when we select one, navigate to their detailed profile page!

If you’re just joining us, here are links to the steps we’ve worked through so far. To get the most out of this tutorial series, I’d suggest checking those out first:

Tidying Up

Before we jump into Lists, let’s clean some thing sup. Though we’ve been confining our UI code to ContentView.swift, this doesn’t mean we need to keep ALL of our Vvews in there.

Go to Project Navigator -> right-click Our Project Folder -> New File. Select a new SwiftUI file and name it ProfilePage.swift. Then, cut our ProfilePage and ProfileInformation structs from ContentView.swift and paste them into the new file.

You’ll notice we still have an error. Our PreviewProvider notices that ProfilePage takes in a Profile param, which means we’ll need to provide one for our Live Preview. This will have no effect on ProfilePage when we compile and build the app, but since we made our view dynamic, it needs some kind of mock data for our preview window. Simply copy and paste our profile into the declaration:

When we go back to ContentView.swift, you’ll notice our Live Preview still works, but our file is now much cleaner. This will be super helpful as we create more Views in the future, especially if we want to export them as packages and such!

Now, onto Lists.

Creating a List View

Previously, we proved our ProfilePage was dynamic by creating another View in only 2 lines of code (one for a new profile object, the other to call the View). But if we have a number of profile objects (lets say 10), do we really want to have ProfilePage(profile: profileX) 10 times in a row? No, we don’t. That’s where Lists come in.

If you’re familiar with loops (e.g. for and while), then the concept is similar. For each iteration of some condition, we run a piece of code. Lists are like a UI loop. Let me walk you through what I mean.

First, create a third profile called profile3 (I made mine Patrick, an Android developer who wishes he was an iOS developer). Then, head over to our Profile class and add the identifiable protocol (I’ll explain more about this in a minute) like this: class Profile: Identifiable{

Lastly, back in our ContentView, remove our two ProfilePages and instead put this:

What we did was create a new List and pass in an Array with our Profiles (that’s what the [] indicate). We then add {}, which sets up a closure to store the code with which we want to run every profile we encounter. And to represent each profile we iterate through, we start the closure by giving a temporary variable named curProfile and mark it with in. Then, we simply call ProfilePage and pass in curProfile.

When our Live Preview updates, you’ll now see 2 full ProfilePages and 1 cutoff on the bottom. No, it’s not broken, it just means we need to scroll!

This also means we’ll need to run our preview…well, live! Hit the Play button on the bottom right of the device window. This will run the app live and allow us to scroll!

Voila! All our profiles are now showing! We accomplished this while also making our code less redundant and more readable in the sense that we know we’re creating multiples of a view for a given list of objects. We’re also now poised to take in even more data should our ‘team’ expand.

While we may like how our profiles are arranged currently, we can do even more. Let’s say we want a birds-eye view (no pun intended) of our team members, and then, after selecting one, see their full ProfilePage. We can accomplish this by setting up a navigation structure in our app with NavigationView. Here’s what I’m envisioning, so you have a visualized goal:

Creating Cells

You’ll notice we still have a List, but now we have something more familiar, like cells in a table. Let’s create that dynamic cell real quick in a new View:

This should all seem familiar, though you’ll notice we’ve used an HStack for the first time. That’s how we accomplish the look in our goal image.

Back in our ContentView, we simply replace ProfilePage with our new ProfileCell, and when we run, we now have our List of cells.

Adding Navigation

Now that we have our minimalist List, we want to be able to choose a cell and see the ProfilePage for the selected cell’s Profile. We’ll follow 2 easy steps to accomplish this.

First, in ContentView, swap out VStack for NavigationView. You won’t see much change, but we’ve now wrapped our List with styling defaults and enabled navigation for the enclosed View hierarchy. One way to test this is by adding the navigationBarTitle modifier. Go ahead and add that to our List: .navigationBarTitle(“SwiftUI Team”)

Second, go to our cell View and wrap our HStack in a NavigationLink. As the name implies, this wraps our cell as a link, taking action when we tap it. We’ll also need to pass in a destination param, which is essentially the View to navigate to when tapped. This is where we’ll call ProfilePage(profile: profile), which will be displayed in a new separate page upon a tap.


We’re all set! Go ahead and hit play in our Live Preview. When it loads, try scrolling (which will just bounce around) and then try tapping a profile. You’ll see we navigate to a ProfilePage that’s standalone. Plus, it’s complete with a way to navigate back on the top left, with the title we set in ContentView!

So there we have it! Our app is starting to look more and more filled out, both visually and feature-wise. Looking at our code, though, we have those 3 large Profile variables that we’ve been playing with. To be honest, it’s cramping our SwiftUI style.

Next time, we talk about how we can read JSON files, keeping data where it belongs and passing it cleanly to our UI.

Here’s the completed code for today:

import SwiftUI

struct ContentView: View {
    var profile: Profile = Profile(name: "Danny", subtitle: "Awesome iOS Developer", description: "Danny loves SwiftUI and thinks it's the future of iOS Development!", profilePic: "profilepic")
    var profile2: Profile = Profile(name: "George", subtitle: "An OK iOS Developer", description: "George should love SwiftUI and think that it's the future of iOS Development!", profilePic: "profilepic2")
    var profile3: Profile = Profile(name: "Patrick", subtitle: "Android Developer", description: "Patrick is in love with SwiftUI and wants to switch to iOS Development!", profilePic: "profilepic2")
    var body: some View {
        NavigationView {
            List([profile, profile2, profile3]){ curProfile in
                ProfileCell(profile: curProfile)
            .navigationBarTitle("SwiftUI Team")

struct ProfileCell : View {
    var profile : Profile
    var body: some View {
        NavigationLink(destination: ProfilePage(profile: profile)){
                    .frame(width: 100, height: 100)

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {

Tip For the Road

Using the Live Preview is great, but if we want to start compiling our app to run in the emulator (or even our personal iPhones), we can simply choose a device and hit play on the top left of Xcode. This will make a compiled build of our app ready for device runtime.

Just remember, though, if our Views (ContentView in particular) are dynamic and rely on mock data in our PreviewProvider, you’ll run into errors since they only feed Live Preview, not builds.

Avatar photo


Our team has been at the forefront of Artificial Intelligence and Machine Learning research for more than 15 years and we're using our collective intelligence to help others learn, understand and grow using these new technologies in ethical and sustainable ways.

Comments 0 Responses

Leave a Reply

Your email address will not be published. Required fields are marked *

wix banner square