Lists, ForEach & Navigation

Reviewed & published by Brayan K

Most apps show lists of data and let you tap a row to see details. You'll build scrollable lists with List, generate rows from data with ForEach and Identifiable, and wire up screen-to-screen navigation with NavigationStack and NavigationLink.

Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

Building iOS Apps with SwiftUI • Intermediate

What You'll Learn in This Lesson

1️⃣ List — Scrollable Rows

List renders a scrollable, platform-styled column of rows, complete with separators. The simplest form just contains a few static views.

import SwiftUI

// List shows a scrollable, platform-styled column of rows. The simplest form
// takes a static set of child views.
struct MenuList: View {
    var body: some View {
        List {
            Text("Espresso")
            Text("Latte")
            Text("Cappuccino")
        }
    }
}

2️⃣ ForEach & Identifiable — Rows from Data

Real lists come from data. ForEach builds one row per element. For SwiftUI to track rows correctly, each element needs a stable identity — conform your model to Identifiable by giving it an id.

import SwiftUI

// To build rows from DATA, your model conforms to Identifiable: it has an
// 'id' that uniquely and stably identifies each value.
struct Task: Identifiable {
    let id = UUID()
    let title: String
}

struct TaskList: View {
    let tasks = [
        Task(title: "Buy milk"),
        Task(title: "Walk dog"),
        Task(title: "Write code")
    ]

    var body: some View {
        // ForEach builds one row per task; List makes it scrollable.
        List {
            ForEach(tasks) { task in
                Text(task.title)
            }
        }
    }
}

For plain values like strings that aren't Identifiable, pass an id key path instead.

import SwiftUI

// If your elements are NOT Identifiable (e.g. plain strings), give ForEach an
// id key path so SwiftUI can tell rows apart.
struct ColorList: View {
    let colors = ["Red", "Green", "Blue"]

    var body: some View {
        List {
            // 'id: \.self' uses each string itself as its identity
            ForEach(colors, id: \.self) { color in
                Text(color)
            }
        }
    }
}

3️⃣ NavigationStack & NavigationLink

Wrap your content in a NavigationStack to enable pushing detail screens. A NavigationLink shows a row that, when tapped, pushes its destination onto the stack. Add a title with .navigationTitle(...).

import SwiftUI

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

// NavigationStack manages pushed screens. A NavigationLink pushes a
// destination when its row is tapped.
struct FruitBrowser: View {
    let fruits = [Fruit(name: "Apple"), Fruit(name: "Pear")]

    var body: some View {
        NavigationStack {
            List {
                ForEach(fruits) { fruit in
                    NavigationLink(fruit.name) {
                        // destination pushed when tapped
                        Text("All about \(fruit.name)")
                    }
                }
            }
            .navigationTitle("Fruits")
        }
    }
}

Your turn. Fill in the blanks to build a navigable library list.

// 🎯 YOUR TURN — fill in each ___ then preview in Xcode.
import SwiftUI

struct Book: ___ {                 // 👉 protocol that requires an 'id'
    let id = UUID()
    let title: String
}

struct Library: View {
    let books = [Book(title: "Swift 101"), Book(title: "SwiftUI Deep Dive")]

    var body: some View {
        ___ {                       // 👉 the container that manages pushed screens
            List {
                ___(books) { book in // 👉 loops over the books
                    NavigationLink(book.title) {
                        Text("Reading \(book.title)")
                    }
                }
            }
            .navigationTitle("Library")
        }
    }
}

Pro Tips

Common Errors (and the fix)

Mini-Challenge: Contacts App

Define a Contact: Identifiable struct with a name and phone number. Build a List inside a NavigationStack that uses ForEach to show each contact's name, with a NavigationLink to a detail screen showing the full name and number. Give the list the title "Contacts".

🎉 Lesson Complete!

Practice quiz

What does the List view do?

  • Draws a single row
  • Shows a scrollable column of rows
  • Sorts an array
  • Stores data

Answer: Shows a scrollable column of rows. List presents data in a scrollable, platform-styled column of rows.

What is ForEach used for in SwiftUI?

  • Looping over a collection to build a view per element
  • Running a background task
  • Repeating an animation
  • Filtering data

Answer: Looping over a collection to build a view per element. ForEach creates one view for each element of a collection.

Why must ForEach elements be identifiable?

  • For sorting
  • So SwiftUI can tell rows apart to update them correctly
  • To save memory
  • It is optional and never needed

Answer: So SwiftUI can tell rows apart to update them correctly. Stable identity lets SwiftUI track, insert, move, and delete the right rows efficiently.

What does conforming to Identifiable require?

  • A name property
  • An id property with a stable, unique value
  • A description method
  • Nothing

Answer: An id property with a stable, unique value. Identifiable requires an id property that uniquely and stably identifies each instance.

How can you give ForEach an id without Identifiable?

  • ForEach(items, key: ...)
  • ForEach(items, id: \.self) or id: \.someProperty
  • ForEach(items).id()
  • You cannot

Answer: ForEach(items, id: \.self) or id: \.someProperty. Pass an id key path, e.g. ForEach(items, id: \.self), when the type isn't Identifiable.

What is NavigationStack?

  • A list of tabs
  • A layout stack
  • A container that manages a stack of pushed screens
  • An animation

Answer: A container that manages a stack of pushed screens. NavigationStack (iOS 16+) manages a navigation stack, pushing and popping detail screens.

What does NavigationLink do?

  • Opens a web URL
  • Pushes a destination view when tapped
  • Dismisses a sheet
  • Reorders a list

Answer: Pushes a destination view when tapped. NavigationLink presents a destination view by pushing it onto the navigation stack when tapped.

Where must a NavigationLink live to work?

  • Anywhere at all
  • Inside a NavigationStack (or NavigationView)
  • Only inside a Button
  • Inside a ZStack

Answer: Inside a NavigationStack (or NavigationView). A NavigationLink needs an enclosing NavigationStack to have a stack to push onto.

How do you add a title to a navigation screen?

  • .title("X")
  • .header("X")
  • .name("X")
  • .navigationTitle("X")

Answer: .navigationTitle("X"). Apply .navigationTitle("X") to the content inside the NavigationStack to set the bar title.

What is a clean way to combine a List with dynamic rows?

  • List with a ForEach over your data
  • A ZStack of Texts
  • Many nested VStacks
  • A single Text

Answer: List with a ForEach over your data. Putting a ForEach inside a List builds one row per element, the standard pattern for dynamic lists.

Continue this course

Frequently asked questions

What is the difference between List and ForEach?

List is a container that renders a scrollable, styled column of rows with separators and platform behaviour. ForEach is a loop that generates one view per element of a collection. You usually nest a ForEach inside a List to turn an array of data into rows, but ForEach also works inside VStack and other containers.

What is Identifiable and why does ForEach need it?

Identifiable is a protocol requiring an id property that uniquely and stably identifies each value. ForEach uses that identity to track rows across updates — so when you insert, move, or delete data, SwiftUI updates exactly the right rows with correct animations. Without stable identity it can't tell which row changed.

How do I use ForEach with plain values like strings?

Plain types like String aren't Identifiable, so pass an id key path: ForEach(colors, id: \.self) uses each value itself as the identity. Use id: \.self only when values are unique; otherwise give your model an explicit unique id property and conform to Identifiable.

What is NavigationStack and NavigationLink?

NavigationStack (iOS 16+) is a container that manages a stack of screens, pushing and popping detail views. NavigationLink is a tappable row that, when tapped, pushes a destination view onto that stack. The link must live inside a NavigationStack so there's a stack to push onto.

How do I set a screen's navigation title?

Apply the .navigationTitle("Your Title") modifier to the content inside the NavigationStack — typically on the List or top container. It sets the title shown in the navigation bar. You can also add toolbar buttons with the .toolbar modifier.