SwiftUI Blog

Mastering SwiftUI: Your Guide to Building Beautiful, Intuitive Apps.

List

A fundamental element of mobile applications is the list. If you think about it, almost every app includes some form of a list. Declaring a list is very simple:

struct ContentView: View {
    var body: some View {
        List  {
            Text("Element One")
            Text("Element Two")            
        }
    }
}

To have:

In this way, we have a scrollable list with fixed elements. However, we can also create the list dynamically using a data set:

struct ContentView: View {
    var names = ["Alice", "Bob", "Charlie"]
    var body: some View {
        List(names, id: \.self) { name in
            Text(name)
        }
    }
}

To have:

The list takes two parameters: an array of names and an ID, which in this case is the name itself. While using the name as a key works for this example, it’s generally not a good solution. Imagine a scenario where the list contains duplicate names, and you need to delete a specific one—it would be impossible to determine which item to remove. To avoid such situations, it’s better to pass an array of data that conforms to the Identifiable protocol. Here’s an example:

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

Thus, we have a struct with name and image properties, along with a unique id.

struct ContentView: View {
    var vehicles = [Vehicle(name: "car", image: "car"),
                    Vehicle(name: "bus", image: "bus"),
                    Vehicle(name: "tram", image: "tram"),
                    Vehicle(name: "bicycle", image: "bicycle")]
    
    var body: some View {
        List(vehicles) { vehicle in
            Text(vehicle.name)
        }
    }
}

To enhance the list, we want to add an image to each row:

struct ContentView: View {
    var vehicles = [Vehicle(name: "car", image: "car"),
                    Vehicle(name: "bus", image: "bus"),
                    Vehicle(name: "tram", image: "tram"),
                    Vehicle(name: "bicycle", image: "bicycle")]
    
    var body: some View {
        List(vehicles) { vehicle in
            HStack {
                Image(systemName: vehicle.image)
                Text(vehicle.name)
            }
        }
    }
}

And we have:

As you can see, the text in the row is not properly aligned. To fix this, we need to make some adjustments: set the resizable property on the image and specify a fixed size for it. Before doing so, it’s better to extract the code that renders the row into a separate subview to keep the code clean. Select the HStack, and from the contextual menu, choose ‘Extract Subview.’ Rename the extracted subview to RowView.

struct RowView: View {
    var body: some View {
        HStack {
            Image(systemName: vehicle.image)
            Text(vehicle.name)
        }
    }
}

There are some errors because the vehicle is not found, so we need to fix it:

struct RowView: View {
    var vehicle: Vehicle
    var body: some View {
        HStack {
            Image(systemName: vehicle.image)
                .resizable()
                .frame(width: 60, height: 60)
            Text(vehicle.name)
        }
    }
}

We also made the image resizable and set a fixed size.
In the ContentView, we now need to make a small change: pass the vehicle to the row.

var body: some View {
        List(vehicles) { vehicle in
            RowView(vehicle: vehicle)
        }
    }

Now we have exactly what we wanted: