Building Lists in SwiftUI

In this article we learn how to create list views in our SwiftUI apps using the `List` structure. We will cover how to populate the contents of the list using a dynamic data source.

Introduction

Presenting textual and graphical information in a column is a very familiar and natural way to visually organize data. Almost all modern apps utilize some sort of columnar data display. Think of popular social media apps that display posts in a timeline, or an e-commerce app that allows you to browse products in a vertical column.

Not surprisingly, SwiftUI provides us with the List structure to help us display columnar views. In this article, we will explore how to use lists as a fundamental building block to create fantastic experiences for our users. Let’s dive in.

Getting started with SwiftUI lists

Let’s start by creating a new project in Xcode. We’re going to make a simple app to help a zoo manage its animals. Let’s call the app Zookeeper, set the interface to SwiftUI and the Life Cycle to SwiftUI App.

Figure 1

We should now see the standard starting point for a SwiftUI project:

Figure 2

Let’s replace the body variable of the ContentView structure with the following code:

var body: some View {
VStack {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
}

The VStack, or vertical stack, organizes its child views in a vertical line. Let’s look at the result of stacking three text views within a VStack:

Figure 3

As we can see, the three text views are stacked vertically as we expected. But there is another way to achieve this visual hierarchy that is more powerful. Let’s replace the contents of the body variable with the following code:

var body: some View {
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
}

Look in the Preview Window and press the Resume button if necessary in order to see a live preview of the code changes you made. You should see something like this:

Figure 4

Awesome! We just created a static list consisting of three text views. As we can see, the List structure acts as a container to vertically list the views defined within it. While it is visually similar to a VStack, the list structure has more functionality. Lists have built-in scrolling, modifiers to help with visual formatting, and the ability to edit the list dynamically.

List modifiers

Like other SwiftUI views, we can apply specific view modifiers to change the styling of our view. Modify your code so the body of ContentView looks like this:

var body: some View {
VStack {
Text("I ❤️ List").font(.headline)
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.listStyle(InsetGroupedListStyle())
}
}

Resume the live preview and you can now see we have a slightly different visual presentation of our list provided by the inset grouped list style. Also, notice how it is possible to include a list as a child view to achieve a more sophisticated visual hierarchy.

Figure 5

Dynamically generated lists

Creating static lists with the List structure couldn’t be simpler, but in practice, we will encounter many situations where we want our lists to visualize an ever-changing stream of data. Consider a news app that has a list of the stories of the day, a music app that shows a list of your favorite songs, or an email app that presents your newest messages in order.

Luckily, the List structure has built-in functionality to help us present arrays of structured data that might be provided by a database or API.

Below your ContentView structure, let’s create a new structure called Animal with two parameters: uniqueID and name:

struct Animal {
var uniqueID : Int
var name: String
}

We will be listing a dynamic array of Animal instances, meaning the data can change over time. When the data changes, the list needs to be able to compare the current data state to the last data state and see what changed. If there isn’t a unique identifier, this might be impossible.

A simple way to understand this concept is to imagine identical twins named Alice. If you took a picture of them standing next to each other, then they switched places and you took another picture, would you be able to tell if they switched places by looking at the two pictures?

The way to solve this conundrum is to have them wear some sort of unique identifier. It could be different color shirts or a tag with a number on it. As long as it is guaranteed to be unique for each twin, you will never get them mixed up. 👯‍♀️ This is why we need to specify the uniqueID property.

Next, let’s create an array of Animal instances:

let animals = [
Animal(uniqueID: 0, name: "Dog"),
Animal(uniqueID: 1, name: "Cat"),
Animal(uniqueID: 2, name: "Parrot")
]

Now let’s use a list to visualize the animals array and modify the body variable of your ContentView so it looks like this:

var body: some View {
List(animals, id: \.uniqueID) { animal in
Text(animal.name)
}
}

There’s a lot going on in this tiny piece of code, so let’s break it all down. The first parameter we provide for the list is the data, which in this case is the animals array. The data needs to have a unique identifier for each element, which we specify using the id parameter. The id parameter expects a keypath, which is what we are defining by using \.uniqueID (a keypath expression).

Swift keypaths are a means to store a reference to a property, without actually storing the value of the property itself. Think of it as a path to the property. The notation \.uniqueID is actually shorthand for \Animal.uniqueID, but the compiler can infer the type so we can leave off the Animal and it still works. Now that we have specified this keypath for the id argument, the list will look at the uniqueID property to identify each element.

The List structure executes the { animal in } closure for each element in the animals array. In this example, we simply show a Text view of the name of each animal.

You should now see this view when we Resume the live preview:

Figure 6

Identifiable protocol

We just saw that each element of the data source for List must be uniquely identifiable. It turns out, there is a protocol that can help with this and ensure that each element is identifiable without having to specify a keypath.

Let’s modify the Animal struct so that it looks like this:

struct Animal: Identifiable {
var id = UUID()
var name : String
}

We have made the Animal structure conform to the Identifiable protocol, which means that it must have a property called id that is a unique identifier. By using an instance of the UUID structure (built into Swift Foundation) we guarantee that id is unique.

Now we don’t need the uniqueID to be specified when we instantiate Animal in the animals array:

let animals = [
Animal(name: "Dog"),
Animal(name: "Cat"),
Animal(name: "Parrot")
]

Because we are giving the list data that is Identifiable, the list automatically knows to look at the ‘id’ property of the data to find its unique identifier. Therefore, we can refactor the List definition to remove the keypath specification:

var body: some View {
List(animals) { animal in
Text(animal.name)
}
}

Making the data dynamic

One of the powerful features of lists is their ability to seamlessly update when their underlying data changes. Imagine a newsfeed app that needs to update as new stories flow in. By adding just a few lines of code, we can see how easy this is to implement.

Let’s delete the animals constant and redefine the ContentView so it looks like this:

struct ContentView: View {
@State var animals = [
Animal(name: "Dog"),
Animal(name: "Cat"),
Animal(name: "Parrot")
]
var body: some View {
VStack {
List(animals) { animal in
Text(animal.name)
}
Button("Add") {
animals.append(Animal(name: "New Animal"))
}
}
}
}

The first modification we made was to change animals to a variable and add the @State property wrapper to let the compiler know to change any views that it is used in when its value changes. Next, we wrapped the list view with a VStack and added a button to the bottom of the stack. The button appends a new instance of Animal to the animals array each time it is pressed. Resume your live preview and see the button in action!

Figure 7

Adding sections

A common visual hierarchy we will want to specify is a list separated by sections. By using the ForEach and Section structures within our List this is easy to accomplish!

ForEach structure

The ForEach structure can compute views from a collection of data that conforms to the Identifiable protocol. Let’s refactor our list to use a ForEach structure:

var body: some View {
List {
ForEach(animals) { animal in
Text(animal.name)
}
}
}

The ForEach executes the { animal in } closure for each element in the animals collection, much like the List did previously. While this accomplishes the same thing as what we had before, we’ll find this quite useful when we add sections to our list.

List sections

A common visual hierarchy you may need to implement in list views is a section view. Let’s see how easy they are to implement with a SwiftUI List.

First, let’s add a new structure under our Animal definition called AnimalGroup:

struct AnimalGroup: Identifiable {
var id = UUID()
var groupName : String
var animals : [Animal]
}

Since we will use a ForEach to dynamically generate the sections, we need to make AnimalGroup conform to Identifiable. Each AnimalGroup has a name and an array of Animals.

Next, let’s replace the animals array with the following array of AnimalGroups:

let animalGroups = [
AnimalGroup(groupName: "Pets", animals: [
Animal(name: "Dog"),
Animal(name: "Cat"),
Animal(name: "Parrot"),
Animal(name: "Mouse")
]),
AnimalGroup(groupName: "Farm", animals: [
Animal(name: "Cow"),
Animal(name: "Horse"),
Animal(name: "Goat"),
Animal(name: "Sheep"),
]),
AnimalGroup(groupName: "Critters", animals: [
Animal(name: "Firefly"),
Animal(name: "Spider"),
Animal(name: "Ant"),
Animal(name: "Squirrel"),
])
]

Here we’re simply instantiating three AnimalGroup structures, each with an array of Animals.

Now for some Section magic! Let’s replace the body of ContentView with this code:

var body: some View {
List {
ForEach(animalGroups) { animalGroup in
Section(header: Text(animalGroup.groupName)) {
ForEach(animalGroup.animals) { animal in
Text(animal.name)
}
}
}
}
}

The first ForEach generates a Section view for each of the AnimalGroup structures in the animalGroups array. The Section view defines a discrete section of the list, which in turn has a nested ForEach that generates the list items for each of the Animal structures in the animal property of its corresponding AnimalGroup. The header of the Section is specified by its header property, which can be any SwiftUI view.

Hit Resume and you should now see this list with sections:

Figure 8

Conclusion

Great job! In this article we learned:

  • How to implement a SwiftUI List view with static data as well as data from a collection.
  • List structures need identifiable data.
  • We can provide identifiable data using keypaths or using the Identifiable protocol.
  • How to use the ForEach structure to generate lists expressively.
  • How to use Section structures to make lists prettier and easier to read.

SwiftUI list views are simple to use, they are extremely powerful for creating wonderful user experiences. You can download the final code from the project at the link here.

Author

Codecademy Team

'The Codecademy Team, composed of experienced educators and tech experts, is dedicated to making tech skills accessible to all. We empower learners worldwide with expert-reviewed content that develops and enhances the technical skills needed to advance and succeed in their careers.'

Meet the full team