How to Implement Lists in SwiftUI

List in SwiftUI allows us to present rows of data arranged in single column with an option to select one or more row items. In this tutorial we will dive into how to create a simple multi-section List. Here is how to do it:

Creating a Datasource

We will start with creating a datasource which we will use to populate the list with. We will create a struct called Place and then create another struct called Continent. We will then create an array of type Place inside the Continent struct and use that as our data source. Make sure that Place struct conforms to Identifiable protocol. More on this protocol later.

Here is the code for it:

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

struct DataSource {
    static var continent = [Place(name: "Asia"),
                            Place(name: "Africa"),
                            Place(name: "South America"),
                            Place(name: "North America"),
                            Place(name: "Australia"),
                            Place(name: "Antartica"),
                            Place(name: "Europe")]
}

Implementing a Simple List in SwiftUI

Next, we will implement the List, so in the `contentView` please write the following:

struct ContentView: View {
    var body: some View {
        List(DataSource.continent) { continent in
            Text(continent.name)
        }
    }
}

That's about it! List gets the data source, and returns the item, which we will add in a Text view.


Adding Sections

Now, lets add sections to this list. We will make these continents as section headers and list countries under each continent (except for Antartica ๐Ÿฅถ). Our datasource will look something like this now:

struct Country: Hashable, Identifiable {
    var id = UUID()
    var countryName : String = ""
}
struct Place: Identifiable {
    var id = UUID()
    var name : String = ""
    var countries = [Country]()

}

struct DataSource {
    static var continent = [Place(name: "Asia",
                                  countries: [Country(countryName: "๐Ÿ‡น๐Ÿ‡ญ Thailand"),Country(countryName: "๐Ÿ‡ฏ๐Ÿ‡ต Japan"),Country(countryName: "๐Ÿ‡ฎ๐Ÿ‡ณ India"),Country(countryName: "๐Ÿ‡ต๐Ÿ‡ฐ Pakistan"),Country(countryName: "๐Ÿ‡ป๐Ÿ‡ณ Vietnam")]),
                            Place(name: "Africa",
                                  countries: [Country(countryName: "๐Ÿ‡ฟ๐Ÿ‡ฆ South Africa"),Country(countryName: "๐Ÿ‡ธ๐Ÿ‡ฉ Sudan"),Country(countryName: "๐Ÿ‡ณ๐Ÿ‡ฌ Nigeria"),Country(countryName: "๐Ÿ‡ณ๐Ÿ‡ฆ Namibia"),Country(countryName: "๐Ÿ‡ฐ๐Ÿ‡ช Kenya")]),
                            Place(name: "South America",
                                  countries: [Country(countryName: "๐Ÿ‡ง๐Ÿ‡ท Brazil"),Country(countryName: "๐Ÿ‡จ๐Ÿ‡ด Colombia"),Country(countryName: "๐Ÿ‡จ๐Ÿ‡ฑ Chile"),Country(countryName: "๐Ÿ‡ฆ๐Ÿ‡ท Argentina"),Country(countryName: "๐Ÿ‡ง๐Ÿ‡ด Bolivia")]),
                            Place(name: "North America",
                                  countries: [Country(countryName: "๐Ÿ‡จ๐Ÿ‡ฆ Canada"),Country(countryName: "๐Ÿ‡บ๐Ÿ‡ธ United States of America")]),
                            Place(name: "Europe",
                                  countries: [Country(countryName: "๐Ÿ‡ซ๐Ÿ‡ท France"),Country(countryName: "๐Ÿ‡ฉ๐Ÿ‡ช Germany"),Country(countryName: "๐Ÿ‡จ๐Ÿ‡ญSwitzerland"),Country(countryName: "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom"),Country(countryName: "๐Ÿ‡ณ๐Ÿ‡ฑ Netherland")]),
                            Place(name: "Oceania",
                                  countries: [Country(countryName: "๐Ÿ‡ฆ๐Ÿ‡บ Australia"),Country(countryName: "๐Ÿ‡ณ๐Ÿ‡ฟ New Zealand"),Country(countryName: "๐Ÿ‡ต๐Ÿ‡ฌ Papua New Guinea")]),
                            Place(name: "Antartica",
                                  countries: [Country(countryName: "๐Ÿฅถ")])

    ]
}

We have a new struct called Country that conforms to Hashable and Identifiable and in Place struct we have an array of type Country. Going back to the content view will now add few things, here is the code:

NavigationView {
    List {
        ForEach(DataSource.continent, id: \.id) { continent in
            Section(header: Text(continent.name)) {
                ForEach(continent.countries, id: \.id) { country in
                    Text(country.countryName)
                }
            }

        }
    }.navigationTitle("๐ŸŒŽ World")
}


First we will wrap our List View with a NavigationView. Inside the List closure, we will run a ForEach loop on the continents array. Please note here that we are providing an id argument as well here. This is used in order to tell the List how to differentiate between elements. The first ForEach closure returns the element of type Continent. We will create the section header using the Section view. We will provide the Continent name as the header and then inside the content closure we will run another ForEach loop but this time on continent.countries array. Note the \.id, in order to identify each country element. This is the reason why our structs conform to Identifiable protocols, so that it is mandatory for us to add the id property for each element. Next, we are just showing the countryName in the Text view.


Implementing Multi-Section

The last thing I would like to implement is the ability to select multiple items from the list. In order to do this just add the following property inside the content view:

@State private var selection = Set<UUID>()


Next, bind this selection property to the List's selection argument and then add an EditButton() to the top navigation bar like this:

NavigationView {
    List(selection: $selection) {
        ....
    }.navigationTitle("๐ŸŒŽ World")
    .toolbar {
        EditButton()
    }
}

So, now you have an edit button on the navigation bar of the app. Tapping it will let you make multiple selections and all those selections will be stored in the selection set.


The full code for this project is available on GitHub.