Skip to content
Prepare to Grow

SwiftUI Fundamentals

Lists, ForEach, and Identifiable Data

Render dynamic collections with stable identity, efficient containers, selection, swipe actions, and safe mutations.

Intermediate6 min read2 guided activities

Before you begin

What you will understand

  • Distinguish List, ScrollView, and lazy containers.
  • Supply stable identity to ForEach.
  • Update and delete collection elements safely.
  • Design useful empty, loading, and filtered states.
01

Learning stage

Concept Explanation

ForEach creates child views from a collection, but it is not itself a scrolling container. List adds platform list behavior such as rows, selection, edit operations, separators, and accessibility conventions. ScrollView with LazyVStack or LazyVGrid is appropriate when presentation needs more control than a platform list.

Identity connects each data element to its corresponding view across insertions, removals, moves, and updates. Identifiable provides a stable ID. Never use a changing property or array index as identity for data that can reorder; SwiftUI may associate state and animation with the wrong row.

Collection screens need more than successful content. Represent loading, empty source, no search results, failure, and populated states deliberately. Apply mutations to the source collection using stable IDs rather than assuming displayed offsets match filtered data.

  • ForEach: transforms identified data into child views.
  • List: platform-adaptive row container with built-in behaviors.
  • Lazy stack/grid: creates scrolling children as needed.
  • Stable identity: remains associated with the same domain entity.
  • Content state: loading, empty, filtered-empty, failure, or populated.
02

Learning stage

Syntax & Examples

The list uses domain identity for rendering and deletes by ID so filtering cannot target the wrong lesson.

LessonList.swiftswift
import SwiftUI

struct Lesson: Identifiable {
  let id: UUID
  let title: String
  var isComplete: Bool
}

struct LessonList: View {
  @State private var lessons = [
    Lesson(id: UUID(), title: "State", isComplete: true),
    Lesson(id: UUID(), title: "Navigation", isComplete: false)
  ]

  var body: some View {
    List {
      if lessons.isEmpty {
        ContentUnavailableView(
          "No lessons",
          systemImage: "book.closed",
          description: Text("Add a lesson to begin learning.")
        )
      } else {
        ForEach(lessons) { lesson in
          Label(
            lesson.title,
            systemImage: lesson.isComplete ? "checkmark.circle.fill" : "circle"
          )
          .swipeActions {
            Button("Delete", role: .destructive) { delete(id: lesson.id) }
          }
        }
      }
    }
  }

  private func delete(id: Lesson.ID) {
    lessons.removeAll { $0.id == id }
  }
}
UUID identity follows the domain entity, and deletion targets that identity rather than a potentially stale display position.
03

Learning stage

Real-World Usage

Connect the SwiftUI concept to complete, maintainable application features.

  • Display lessons, messages, products, settings, search results, and downloads.
  • Add row actions, selection, reordering, and deletion.
  • Build adaptive card grids with lazy containers.
  • Preserve row-local state during sorting and filtering.
04

Learning stage

Common Mistakes

Identify patterns that weaken ownership, identity, adaptation, accessibility, or user experience.

  • Using id: .self for mutable values that can duplicate or change.
  • Using array indices as identity for inserted, deleted, or reordered domain data.
  • Deleting source items using offsets from a filtered list without mapping correctly.
  • Placing thousands of eager children in a normal VStack inside ScrollView.
  • Showing an empty white screen instead of explaining the state and recovery action.
05

Learning stage

Practice Exercises

Build the behavior in a small Xcode project and review it in multiple states.

Activity 1

Build a searchable lesson list

Create identified lesson data, a derived filtered collection, and separate states for no lessons and no matching results.

Hint: Filter from the source; do not replace the source collection with search results.

Activity 2

Delete from a filtered view

Add deletion to a filtered list by resolving displayed IDs back to the source collection.

Hint: Identity is safer than assuming displayed and source offsets are equal.

06

Learning stage

Knowledge Check

Answer each question before revealing the explanation.

QUESTION 1

Why does ForEach need stable identity?

  1. A.To associate the same data entity with its view across updates
  2. B.To make every row blue
  3. C.To fetch network data
  4. D.To avoid using structs
Reveal answer

To associate the same data entity with its view across updates

State, transitions, updates, and accessibility behavior depend on SwiftUI recognizing which entity is which.

QUESTION 2

Does ForEach provide scrolling by itself?

Reveal answer

No. It creates child views and must be placed in a container such as List, ScrollView, or a stack.

The surrounding container determines layout, scrolling, and platform list behavior.

07

Learning stage

Interview Preparation

Explain the design in terms of data flow, identity, lifecycle, platform behavior, and trade-offs.

What makes an ID suitable for SwiftUI collection identity?

Short answer: It is unique among siblings and remains stable for the lifetime of the same domain entity.

Explore the detailed answer

A database ID or persisted UUID is usually appropriate. An index changes when items move, and a mutable title changes when edited. Unstable identity can recreate rows, attach state to the wrong item, or produce incorrect animations and updates.

Possible follow-ups

  • • When is id: .self safe?
  • • How would you delete from filtered data?
08

Learning stage

Key Takeaways

Dynamic SwiftUI collections depend on stable domain identity. Choose the right container, preserve the source collection, mutate by ID, and design the non-content states as carefully as the rows.

  • ForEach creates identified children; it is not a scroll view.
  • List provides platform row behavior.
  • Stable IDs must not change with ordering or editing.
  • Mutate filtered data by domain identity.
  • Design loading, empty, failure, and no-result states.