Building Lists in SwiftUI
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
.
We should now see the standard starting point for a SwiftUI project:
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
:
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:
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.
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 : Intvar 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 inText(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:
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 inText(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 inText(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!
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 inText(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 : Stringvar 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 Animal
s.
Next, let’s replace the animals
array with the following array of AnimalGroup
s:
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 Animal
s.
Now for some Section
magic! Let’s replace the body
of ContentView
with this code:
var body: some View {List {ForEach(animalGroups) { animalGroup inSection(header: Text(animalGroup.groupName)) {ForEach(animalGroup.animals) { animal inText(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:
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
'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