100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Swift

List and ForEach

Learn how List renders scrollable, platform-styled collections and how ForEach generates repeated views from a data collection using stable identity.

Lists & ScrollingBeginner9 min readJul 8, 2026
Analogies

List and ForEach

List is SwiftUI's container for displaying scrollable, platform-native collections of rows—it handles scrolling, row separators, section headers, swipe actions, and selection for you, mirroring UITableView/UICollectionView behavior on iOS. ForEach is a more general-purpose construct: it generates a view for each element of any RandomAccessCollection, and can be used inside List, inside a VStack, or anywhere else multiple views need to be produced from a collection. The two are frequently combined—List provides the scrollable chrome, ForEach supplies the repeated content—but they solve different problems and neither requires the other.

🏏

Cricket analogy: Just as a stadium's scoreboard handles the overall display chrome — scrolling overs, section breaks between innings — while the ball-by-ball commentary feed generates one entry per delivery, List provides scrollable chrome and ForEach supplies repeated content, and neither requires the other.

Identity: The Core Requirement

SwiftUI needs a stable identity for every row so it can efficiently diff which elements were added, removed, or reordered between renders, rather than redrawing everything. ForEach expresses this either by requiring the data's elements to conform to Identifiable (with a stable id property) or by supplying an explicit key path via ForEach(data, id: \.someProperty). Using an unstable identity—like array index for a mutable array—causes SwiftUI to misattribute state (e.g. a text field's content 'jumping' to the wrong row) when items are inserted, removed, or reordered.

🏏

Cricket analogy: Just as mixing up two players both wearing jersey number 7 on different teams would misattribute a wicket to the wrong batsman, using array index instead of a stable id in ForEach can make SwiftUI misattribute a text field's content to the wrong row when the list reorders.

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

struct TaskListView: View {
    @State private var tasks: [Task] = [
        Task(title: "Write proposal", isDone: false),
        Task(title: "Review PR", isDone: true)
    ]

    var body: some View {
        List {
            ForEach(tasks) { task in
                HStack {
                    Text(task.title)
                    Spacer()
                    if task.isDone {
                        Image(systemName: "checkmark.circle.fill")
                            .foregroundStyle(.green)
                    }
                }
            }
            .onDelete { indexSet in
                tasks.remove(atOffsets: indexSet)
            }
        }
    }
}

Sections, Styles, and Row Modifiers

List supports Section for grouping rows under headers and footers, and a listStyle(_:) modifier (.plain, .grouped, .insetGrouped, .sidebar) to match different platform conventions. Row-level interactivity comes from modifiers like .swipeActions, .onDelete, and .onMove (the latter two typically paired with an .editMode / EditButton toggle), letting you build fully native list-editing experiences with only a few lines of declarative code instead of manually managing UITableViewDataSource/UITableViewDelegate as in UIKit.

🏏

Cricket analogy: Just as a scorecard groups deliveries under over-headers with a format switch between Test and T20 display, swiping a fielder's card to substitute them replaces the manual bookkeeping a scorer once tracked by hand, mirroring Section, listStyle, and swipeActions replacing UITableViewDataSource code.

swift
List {
    Section("Pending") {
        ForEach(tasks.filter { !$0.isDone }) { task in
            Text(task.title)
        }
    }
    Section("Completed") {
        ForEach(tasks.filter { $0.isDone }) { task in
            Text(task.title)
                .strikethrough()
        }
    }
}
.listStyle(.insetGrouped)

ForEach isn't just for List—ForEach(1...3) { number in Text("Item \(number)") } works just as well inside a plain VStack or HStack. List is a scrollable, styled container; ForEach is purely a view-generating loop construct that can live inside List, a Stack, a Grid, or elsewhere.

Using ForEach(0..<tasks.count, id: \.self) { index in ... } and indexing into the array inside the closure is a common anti-pattern: it re-introduces the index-as-identity problem List/ForEach is designed to avoid, and it makes deletions/insertions error-prone since the closure captures an index, not a stable identity.

  • List provides scrolling, native row styling, sections, and platform-consistent list chrome.
  • ForEach is a general view-generating loop over a collection, usable inside or outside List.
  • Stable identity (via Identifiable or an explicit id key path) lets SwiftUI correctly diff insertions, deletions, and reorders.
  • Avoid using raw array indices as ForEach identity for mutable collections—it causes state to attach to the wrong row.
  • Section groups rows with headers/footers; listStyle(_:) adapts List's appearance to platform conventions.
  • .onDelete and .onMove attach native swipe-to-delete and drag-to-reorder behavior to a ForEach inside a List.

Practice what you learned

Was this page helpful?

Topics covered

#Swift#IOSWithSwiftUIStudyNotes#MobileDevelopment#ListAndForEach#List#ForEach#Identity#Core#Loops#DataStructures#StudyNotes#SkillVeris