Labs ICT
Pro Login

Lists and ForEach

Displaying dynamic data.

List with Static Content

The simplest way to show a list is to put views directly inside a List. Each child becomes a row. List handles scrolling, selection, and platform-specific styling automatically.

List {
    Text("Milk")
    Text("Eggs")
    Text("Bread")
    Text("Butter")
}
Try it Yourself ->

ForEach for Dynamic Data

Static lists are fine for simple cases, but real apps display data from arrays and models. ForEach loops over your data and creates a view for each item. It's the workhorse behind dynamic lists.

let groceries = ["Milk", "Eggs", "Bread", "Butter", "Cheese"]

struct DynamicListView: View {
    var body: some View {
        List {
            ForEach(groceries, id: \.self) { item in
                Text(item)
            }
        }
    }
}
Try it Yourself ->

Identifiable Protocol

ForEach needs a way to uniquely identify each item. If your data conforms to Identifiable, it just works. Each item gets an id property and Swift uses it to track rows efficiently.

struct Task: Identifiable {
    let id = UUID()
    var title: String
    var isComplete: Bool
}

struct TaskListView: View {
    let tasks = [
        Task(title: "Buy groceries", isComplete: false),
        Task(title: "Clean house", isComplete: true),
        Task(title: "Write code", isComplete: false)
    ]

    var body: some View {
        List(tasks) { task in
            HStack {
                Image(systemName: task.isComplete ? "checkmark.circle.fill" : "circle")
                Text(task.title)
            }
        }
    }
}
Try it Yourself ->

Swipe Actions and Refreshable

Lists can respond to swipes and pull-to-refresh. Add .swipeActions for context-sensitive buttons, and .refreshable to load fresh data when the user pulls down.

List(items) { item in
    Text(item.name)
        .swipeActions(edge: .trailing) {
            Button(role: .destructive) {
                delete(item)
            } label: {
                Label("Delete", systemImage: "trash")
            }
        }
        .swipeActions(edge: .leading) {
            Button {
                toggleComplete(item)
            } label: {
                Label("Complete", systemImage: "checkmark")
            }
            .tint(.green)
        }
}
.refreshable {
    await loadItems()
}
Try it Yourself ->

List Styles

Change how a list looks with the .listStyle modifier. Choose from inset, grouped, plain, or sidebar styles depending on your platform and design.

List {
    Section("Favorites") {
        Text("Coffee")
        Text("Tea")
    }
    Section("Recent") {
        Text("Water")
        Text("Juice")
    }
}
.listStyle(.insetGrouped)
Try it Yourself ->

NavigationStack and NavigationLink

Wrap your list in a NavigationStack and use NavigationLink to let users tap into detail views. The navigation stack manages the push/pop history for you.

struct Item: Identifiable {
    let id = UUID()
    let name: String
    let detail: String
}

struct ItemListView: View {
    let items = [
        Item(name: "Swift", detail: "A powerful programming language."),
        Item(name: "SwiftUI", detail: "Declarative UI framework."),
        Item(name: "Xcode", detail: "Apple's IDE.")
    ]

    var body: some View {
        NavigationStack {
            List(items) { item in
                NavigationLink(item.name) {
                    VStack(alignment: .leading, spacing: 8) {
                        Text(item.name)
                            .font(.title)
                        Text(item.detail)
                            .foregroundColor(.secondary)
                    }
                    .padding()
                }
            }
            .navigationTitle("Topics")
        }
    }
}
Try it Yourself ->