Skip to Content
Course content

94: List and ForEach in SwiftUI

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've been thinking about how to organize my current side project tasks, so instead of using a Trello board, I figured I'd just build a quick backlog viewer in SwiftUI. It seems simple enough, but this is where we usually run into the difference between "just showing some data" and "building a proper list."

The "Manual" Way

If I just have a few tasks, I could probably just stack them. Let's see what happens if I use a VStack and some hardcoded strings.

struct BacklogView: View {
    var body: some View {
        VStack {
            Text("Fix memory leak in Parser")
            Text("Update README")
            Text("Add unit tests for Auth")
        }
    }
}

It works, but it's hideous. Everything is bunched up in the center of the screen, there are no dividers, and if I add twenty more tasks, they'll just bleed off the top and bottom of the screen because a VStack doesn't scroll by default. This is clearly not how we want to handle a dynamic set of data.

Letting ForEach do the heavy lifting

I don't want to hardcode every single task. I'll put them in an array and use ForEach. Now, here is a common point of confusion: ForEach in SwiftUI isn't exactly a "for loop" like you'd find in a standard Swift function. It's actually a view that generates other views.

let tasks = ["Fix memory leak", "Update README", "Add unit tests"]

var body: some View {
    VStack {
        ForEach(tasks, id: \.self) { task in
            Text(task)
        }
    }
}

That id: \.self part? That's the "magic" that usually trips people up. SwiftUI needs to know which item is which so that if one task changes or gets deleted, it doesn't have to redraw the entire screen. By saying \.self, I'm telling SwiftUI, "The string itself is the unique identifier." This works for simple strings, but it's a nightmare if you have two tasks named "Fix bug"—SwiftUI will get confused about which one you're interacting with.

Switching to a proper List

The VStack with ForEach still doesn't feel like a real iOS app. It's just a pile of text. Let's swap that VStack for a List.

var body: some View {
    List {
        ForEach(tasks, id: \.self) { task in
            Text(task)
        }
    }
}

Immediately, everything looks professional. We've got the standard system spacing, the separators between rows, and—most importantly—it scrolls automatically. Interestingly, you can actually put the array directly into the List without an explicit ForEach, but I prefer keeping the ForEach inside because it gives us more control if we want to mix in other types of views (like a header or a section) later on.

Solving the Identity Crisis

Now, a real app wouldn't just use strings. My tasks need IDs, priority levels, and completion statuses. Let's try to use a struct.

struct ProjectTask {
    var name: String
    var priority: Int
}

let myTasks = [
    ProjectTask(name: "Fix memory leak", priority: 1),
    ProjectTask(name: "Update README", priority: 3)
]

If I try to throw myTasks into a ForEach now, the compiler starts screaming at me. It's demanding that the data be Identifiable. Instead of typing id: \.name every single time (which is risky because names can overlap), the professional move is to make the struct conform to the Identifiable protocol.

struct ProjectTask: Identifiable {
    let id = UUID() // This generates a unique ID automatically
    var name: String
    var priority: Int
}

// Now the ForEach becomes much cleaner:
ForEach(myTasks) { task in
    Text(task.name)
}

By adding Identifiable, I've removed the need to specify the id parameter in the ForEach. SwiftUI just looks for the id property on the object. It's cleaner, safer, and it's how almost every professional SwiftUI project handles lists of data.




📋 Practical Task

Build a "Favorite Coffee Shop" Directory

Create a view that displays a list of coffee shops. You will need to:

  • Define a CoffeeShop struct that conforms to Identifiable. It should have a name (String) and a signatureDrink (String).
  • Create an array of at least four CoffeeShop instances.
  • Use a List and ForEach to display these shops.
  • Inside the ForEach, use a VStack (with alignment .leading) for each row so that the shop name is prominent and the signature drink is displayed underneath it in a smaller, secondary font color.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.