Complete Guide to List in SwiftUI

swiftui Aug 15, 2024
List in SwiftUI

In SwiftUI, a List is a container view that displays a collection of data in a vertically stacked format. List is typically used to display large collection of data and provides scrolling by default when the number of rows exceeds the length of the screen. It is commonly used to create dynamic, scrollable lists of items, such as menus.

Additionally, SwiftUI's List loads its content lazily, meaning only the currently visible cells or rows are loaded into memory, resulting in reduced memory usage.

Table of Contents

  1. How to build SwiftUI List
  2. Dynamic List from a collection of data
  3. Using ForEach in SwiftUI List
  4. How to add sections to a List
  5. Styling SwiftUI List
  6. Using List for Navigation
  7. How to allow row selection in List
  8. How to Delete List rows from SwiftUI List using onDelete
  9. How to reorder List rows
     

How to build SwiftUI List

To build a SwiftUI List, you simply have to use a built-in component called List and stack the views that you want to display inside it.

struct ContentView: View {
    var body: some View {
        List {
            Text("Account")
            Text("Privacy")
            Button("Invite a friend") {}
            LabeledContent("App Version", value: "16.7")
        }
    }
}

Note: This approach is suitable when the number of components to be shown are limited.

Result:

 

Dynamic List from a collection of data

In most of the use cases, you would want to create a list dynamically from a collection of data. Let's say, you want to build a workout app where the exercises are listed.

One key thing to note in here is, when creating a dynamic List, SwiftUI needs a way to uniquely identify each item in the collection and this can be achieved primarily in the following two ways.

1. Using a unique id parameter - For this approach, you have to pass in id parameter of the type key path to the List. You can either use the hash of the object as id by passing id: \.self, else you can create a unique property for each element in your collection and pass it as id.

struct ContentView: View {
    var absExercises = ["Crunches", "Plank", "Mountain Climbers", "Jumping Jack"]
    
    var body: some View {
        List(absExercises, id: \.self) {
            Text($0)
        }
    }
}

2. Confirming to Identifiable protocol - Another approach is to create a separate model for defining the structure of the data elements which confirms to the Identifiable protocol. In this case, SwiftUI automatically use the required id property for uniquely identifying elements.

Let's first create a model for the exercise.

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

Here, we have initialised id to UUID(). You can use any other value as well ensuring that it never contains duplicate.

Now, you can define the collection and pass it to the List.

struct ContentView: View {
    var absExercises = [
        Exercise(name: "Crunches"),
        Exercise(name: "Plank"),
        Exercise(name: "Mountain Climbers"),
        Exercise(name: "Jumping Jack")
    ]
    
    var body: some View {
        List(absExercises) { exercise in
            Text(exercise.name)
        }
    }
}

Result:

 

Using ForEach in SwiftUI List

For displaying a collection of data in a List, you can also use ForEach to loop over each item in the collection.

Now you might be thinking, why is this approach even required? Let's say, you want to display two or more collections or along with that you also want to show some other views inside the List. In such cases, you will have to make use of ForEach for iterating over the individual collections.

Let's say, you have another category of legs exercises. You want to display both the collections together in the same List along with two buttons, one for adding a new legs exercise and other one for removing an existing one.

struct ContentView: View {
    var absExercises = [
        Exercise(name: "Crunches"),
        Exercise(name: "Plank"),
        Exercise(name: "Mountain Climbers"),
        Exercise(name: "Jumping Jack")
    ]
    
    @State private var legsExercises = [
        Exercise(name: "Lunges"),
        Exercise(name: "Squat")
    ]
    
    var body: some View {
        List {
            ForEach(absExercises){ exercise in
                Text(exercise.name)
            }
            
            ForEach(legsExercises){ exercise in
                Text(exercise.name)
            }
            
            Button ("Add New Exercise"){
                legsExercises.append(Exercise(name: "Calf raises"))
            }
            
            Button ("Remove Exercise"){
                legsExercises.remove(at: legsExercises.count - 1)
            }
        }
    }
}

Result:

 

How to add sections to a List

To better structure a List, SwiftUI also provides the flexibility to divide the content of a List into sections. Optionally, you can also give each section a header as well a footer.

Let's say, you want to create separate sections, one for Abs exercises and other one for Legs exercises.

var body: some View {
    List {
        Section {
            ForEach(absExercises){ exercise in
                Text(exercise.name)
            }
        } header: {
            Text("Abs Exercises")
                .fontWeight(.semibold)
                .foregroundStyle(.blue)
        }
            
        Section {
            ForEach(legsExercises){ exercise in
                Text(exercise.name)
            }
        } header: {
            Text("Legs Exercises")
                .fontWeight(.semibold)
                .foregroundStyle(.blue)
        } footer: {
            Text("Push hard & don't skip leg day")
        }
    }
}

Result:

 

Styling SwiftUI List

When you implement a List, you would observe that the appearance of the list varies across platforms and also on the type of view it is in. That is because, SwiftUI provides platform specific styling to List.

You can modify the appearance of a List using the listStyle modifier. If you don't specify a listStyle, the default style .automatic is applied. For iOS, the default list style (.automatic) is .insetGrouped.

There are primarily five different styles available for iOS namely .plain, .grouped, .insetGrouped, .sidebar and .inset. There are some other styles as well which are available for other platforms.

Let's apply the .inset list style in our example.

List {
    ForEach(absExercises){ exercise in
        Text(exercise.name)
    }
}
.listStyle(.inset)

Result:

 

Using List for Navigation

In SwiftUI, List lets you display both static content, such as fixed rows, and dynamic content, like navigational links that take you to other views. For enabling navigation, you will have to wrap your List in a NavigationStack and to make the list clickable you'll have to use NavigationLink for taking user from one screen to another.

var body: some View {
    NavigationStack {
        List {
            ForEach(absExercises){ exercise in
                NavigationLink(exercise.name) {
                    Text(exercise.name)
                        .font(.largeTitle)
                }
            }
        }
    }
}

Result:

 

How to allow row selection in List

List in SwiftUI supports both single and multiple selection of rows. To implement list selection, you'll have to use the List initialiser with selection parameter.

This parameter is binding to an individual value or a collection depending on whether the selection is single or multiple. It is required so that it is easy to uniquely identify the row/rows selected. For this example, you can use the id property which can uniquely identify each selected row.

1. Single Selection:

Let's first create a @State variable of type UUID and initialise it to nil.

@State private var singleSelection: UUID? = nil

Now, you simply have to bind the List to singleSelection.

var body: some View {
        List (selection: $singleSelection) {
                ForEach(absExercises){ exercise in
                    Text(exercise.name)
        }
    }
}

Result:

2. Multiple Selection: Let's say, you want to delete multiple selected exercises at once.

Again, you'll have to first create a @State variable of collection type. Since, this collection must contain unique values, we'll make it a Set type and initialise it to an empty Set. When the delete action will be performed, we want to display the modified array of exercises, so make sure that it is also defined as a @State variable.

@State private var multiSelection: Set<UUID> = []

@State private var absExercises = [
        Exercise(name: "Crunches"),
        Exercise(name: "Plank"),
        Exercise(name: "Mountain Climbers"),
        Exercise(name: "Jumping Jack")
]

Now, let's bind the List to multiSelection and use SwiftUI's EditButton() to toggle the edit mode.

NavigationStack {
    List (selection: $multiSelection) {
        ForEach(absExercises){ exercise in
            Text(exercise.name)
        }
    }
    .navigationTitle("Exercises")
    .toolbar {
        EditButton()
    }
}

Now, all the selected ids will be stored in the multiSelection variable. You can now add a button to delete the selected exercises based on it's id.

if(!multiSelection.isEmpty){
    Button("Remove Selected Exercises") {
        absExercises.removeAll { element in
            multiSelection.contains(element.id)
        }
    }
}

Result:

 

How to Delete List rows from SwiftUI List using onDelete

Swipe to delete action is very common in apps which heavily make use of List, for example, Whatsapp, LinkedIn etc. It's implementation is pretty simple, you only have to use the .onDelete modifier to swipe to delete list items.

var body: some View {
    List () {
        ForEach(absExercises){ exercise in
            Text(exercise.name)
        }
        .onDelete(perform: { indexSet in
            absExercises.remove(atOffsets: indexSet)
        })
    }
}

In the above code, when a user swipes a row, the closure provided to perform: is executed. It takes a value of type IndexSet as the parameter representing the index position of the item that the user has swiped. Then, the remove(atOffsets:) method is called to remove the swiped element from absExercises collection.

Result:

 

How to reorder List rows

Similar to delete, you can also add in reorder row functionality in your List with the .onMove modifier.

var body: some View {
    List () {
        ForEach(absExercises){ exercise in
            Text(exercise.name)
        }
        .onMove(perform: { indexSet, newIndex in
                absExercises.move(fromOffsets: indexSet, toOffset: newIndex)
        })
    }
}

In the above code, when a user selects and move a row, the closure provided to perform: is executed which takes two values as the parameters. One is of type IndexSet representing the index position of the item that the user is moving and other of type Int which is the index position where the moved item is moved to.

Then, we are using move(fromOffsets:, toOffset:) method to change the position of the selected element in absExercises collection.

Result:

 

Where to go next?

Congratulations, you have successfully learned how to implement List in SwiftUI, different ways of handling collections in a List, how to use List for navigation, structuring a List better with Sections, how to add interactivity to a List and styling it's appearance.

We would highly recommend you to learn about more advance concepts in SwiftUI like NavigationStack, Unit Testing and MVVM architecture.

Signup now to get notified about our
FREE iOS Workshops!