Displaying data in lists
Visualize collections of data with platform-appropriate appearance.
Overview
Displaying a collection of data in a vertical list is a common requirement in many apps. Whether it’s a list of contacts, a schedule of events, an index of categories, or a shopping list, you’ll often find a use for a List.
List views display collections of items vertically, load rows as needed, and add scrolling when the rows don’t fit on the screen, making them suitable for displaying large collections of data.
By default, list views also apply platform-appropriate styling to their elements. For example, on iOS, the default configuration of a list displays a separator line between each row, and adds disclosure indicators next to items that initiate navigation actions.
The code in this article shows the use of list views to display a company’s staff directory. Each section enhances the usefulness of the list, by adding custom cells, splitting the list into sections, and using the list selection to navigate to a detail view.
Prepare your data for iteration
The most common use of List is for representing collections of information in your data model. The following example defines a Person as an Identifiable type with the properties name and phoneNumber. An array called staff contains two instances of this type.
struct Person: Identifiable {
let id = UUID()
var name: String
var phoneNumber: String
}
var staff = [
Person(name: "Juan Chavez", phoneNumber: "(408) 555-4301"),
Person(name: "Mei Chen", phoneNumber: "(919) 555-2481")
]To present the contents of the array as a list, the example creates a List instance. The list’s content builder uses a ForEach to iterate over the staff array. For each member of the array, the listing creates a row view by instantiating a new Text that contains the name of the Person.
struct StaffList: View {
var body: some View {
List {
ForEach(staff) { person in
Text(person.name)
}
}
}
}[Image]
Members of a list must be uniquely identifiable from one another. Unique identifiers allow SwiftUI to automatically generate animations for changes in the underlying data, like inserts, deletions, and moves. Identify list members either by using a type that conforms to Identifiable, as Person does, or by providing an id parameter with the key path to a unique property of the type. The ForEach that populates the list above depends on this behavior, as do the List initializers that take a RandomAccessCollection of members to iterate over.
Display data inside a row
Each row inside a List must be a SwiftUI View. You may be able to represent your data with a single view such as an Image or Text view, or you may need to define a custom view to compose several views into something more complex.
As your row views get more sophisticated, refactor the views into separate view structures, passing in the data that the row needs to render. The following example defines a PersonRowView to create a two-line view for a Person, using fonts, colors, and the system “phone” icon image to visually style the data.
struct PersonRowView: View {
var person: Person
var body: some View {
VStack(alignment: .leading, spacing: 3) {
Text(person.name)
.foregroundColor(.primary)
.font(.headline)
HStack(spacing: 3) {
Label(person.phoneNumber, systemImage: "phone")
}
.foregroundColor(.secondary)
.font(.subheadline)
}
}
}
struct StaffList: View {
var body: some View {
List {
ForEach(staff) { person in
PersonRowView(person: person)
}
}
}
}[Image]
For more information on composing the types of views commonly used inside list rows, see Building layouts with stack views.
Represent data hierarchy with sections
List views can also display data with a level of hierarchy, grouping associated data into sections.
Consider an expanded data model that represents an entire company, including multiple departments. Each Department has a name and an array of Person instances, and the company has an array of the Department type.
struct Department: Identifiable {
let id = UUID()
var name: String
var staff: [Person]
}
struct Company {
var departments: [Department]
}
var company = Company(departments: [
Department(name: "Sales", staff: [
Person(name: "Juan Chavez", phoneNumber: "(408) 555-4301"),
Person(name: "Mei Chen", phoneNumber: "(919) 555-2481"),
// ...
]),
Department(name: "Engineering", staff: [
Person(name: "Bill James", phoneNumber: "(408) 555-4450"),
Person(name: "Anne Johnson", phoneNumber: "(417) 555-9311"),
// ...
]),
// ...
])Use Section views to give the data inside a List a level of hierarchy. Start by creating the List, using a ForEach to iterate over the company.departments array, and then create Section views for each department. Within the section’s view builder, use a ForEach to iterate over the department’s staff, and return a customized view for each Person.
List {
ForEach(company.departments) { department in
Section {
ForEach(department.staff) { person in
PersonRowView(person: person)
}
} header: {
Text(department.name)
}
}
}[Image]
Use Lists for Navigation
Using a NavigationLink within a List contained inside a NavigationStack adds platform-appropriate visual styling for navigation. SwiftUI navigates to a destination view you provide when a person chooses a list item.
The following example sets up a navigation-based UI by wrapping the list with a navigation stack. Instances of NavigationLink wrap the list’s rows to provide a destination view to navigate to when a person taps the row.
NavigationStack {
List {
ForEach(company.departments) { department in
Section {
ForEach(department.staff) { person in
NavigationLink {
PersonDetailView(person: person)
} label: {
PersonRowView(person: person)
}
}
} header: {
Text(department.name)
}
}
}
.navigationTitle("Staff Directory")
}In this example, the view passed in as the destination is a PersonDetailView, which repeats the information from the list. In a more complex app, this detail view could show more information about a Person than would fit inside the list row.
struct PersonDetailView: View {
var person: Person
var body: some View {
VStack {
Text(person.name)
.foregroundColor(.primary)
.font(.title)
.padding()
HStack {
Label(person.phoneNumber, systemImage: "phone")
}
.foregroundColor(.secondary)
}
}
}For more information about navigation stacks, see Understanding the navigation stack.