List — what it is, the list component in SwiftUI

Author: IT Sectr Published: 2026-06-25 Reading time: 7 min

List is a container View in SwiftUI for displaying data as a vertically scrollable list, analogous to UITableView in UIKit. According to Apple Developer Documentation, 2024, List supports static and dynamic sections, swipe actions, row reordering and pull-to-refresh. Unlike UITableView, List uses a declarative API based on SwiftUI and ForEach, automatically managing cell reuse and performance with a large number of rows.

Key Takeaways

  • List — container for a scrollable data list in SwiftUI
  • ForEach — the primary way to dynamically display data in List
  • Sections — Section View for grouping rows with headers
  • Swipe actions — swipeActions for iOS 15+
  • Pull-to-refresh — .refreshable for iOS 15+

What is List in SwiftUI?

List is a View that displays a sequence of items in a vertically scrollable list. It was introduced in iOS 13 alongside SwiftUI and is the primary way to display lists of data, replacing UITableView from UIKit. List automatically manages cell reuse, scrolling, and performance.

List uses lazy-loading: cells are created as you scroll, not all at once. This sets it apart from VStack with ForEach inside ScrollView, where all cells are created at render time. List also provides built-in support for swipe actions, pull-to-refresh, editing (delete/move), and row selection.

According to Apple WWDC 2021 (Session 10072), List in iOS 15+ received significant performance improvements thanks to a new diffing mechanism at the collection level. This made List more efficient when updating data, especially for lists with hundreds of rows.

List vs ScrollView + VStack

Developers often choose between List and ScrollView with VStack for displaying a set of Views. The key difference: List uses cell reuse (like UITableView), while ScrollView + VStack creates all Views immediately. For fixed-size lists (up to 20 items), the difference is negligible. For dynamic lists with 50+ rows, List is preferable for performance.

Static and dynamic lists

Static List is a list with a fixed number of rows specified directly in the List body. It is used for menus, settings, and forms with a known set of items. Each row is declared explicitly, without loops or ForEach.

swift
// Static list (for menus and settings)
List {
    Text("Profile")
    Text("Settings")
    Text("About")
}

// Dynamic list (for data)
struct UserList: View {
    let users: [User]

    var body: some View {
        List(users) { user in
            HStack {
                Text(user.name)
                Text(user.role)
                    .foregroundColor(.secondary)
            }
        }
    }
}

Dynamic List uses the List(data:rowContent:) initializer or ForEach inside the List body. The first approach is convenient when each row corresponds to one data item. The second is useful when there are sections or additional items between data.

Identification (Identifiable): For dynamic lists, data items must conform to the Identifiable protocol, or you must specify a KeyPath to a unique identifier in the data:id tuple. SwiftUI uses identifiers to track changes: additions, deletions, and row moves.

Sections and data grouping

Section is a View for grouping rows in a List with a header and an optional footer. Section accepts header and footer as ViewBuilder, allowing you to use not only text but also custom Views for section headers.

swift
struct SettingsView: View {
    var body: some View {
        List {
            Section(header: Text("Account")) {
                Text("Name")
                Text("Email")
            }
            Section(header: Text("Notifications")) {
                Toggle("Push", isOn: $pushEnabled)
                Toggle("Email", isOn: $emailEnabled)
            }
        }
        .listStyle(.insetGrouped)
    }
}

// Dynamic sections with ForEach
List {
    ForEach(groupedData.keys.sorted(), id: \.self) { key in
        Section(header: Text(key)) {
            ForEach(groupedData[key]!) { item in
                Text(item.title)
            }
        }
    }
}

List Styles: SwiftUI provides several built-in styles through the .listStyle() modifier. .insetGrouped — standard for iOS Settings, .plain — minimalistic, .inset — with indentation, .sidebar — for Sidebar on iPad.

According to SwiftUI Cookbook (2024), Section with dynamic sections and ForEach inside is a standard pattern for grouping data in applications with complex structure. The key rule: do not nest Section inside Section, and do not use the List(data:) initializer together with Section — use ForEach inside the List body.

Swipe actions and pull-to-refresh

.swipeActions(edge:allowsFullSwipe:content:) — a modifier for iOS 15+ that adds swipe actions to List rows. It allows displaying buttons when swiping left (default) or right, with different colors and roles (destructive, cancel).

swift
struct TaskList: View {
    @Binding var tasks: [Task]

    var body: some View {
        List {
            ForEach($tasks) { $task in
                Text(task.title)
                    .swipeActions(edge: .trailing) {
                        Button("Delete", role: .destructive) {
                            tasks.removeAll { $0.id == task.id }
                        }
                    }
                    .swipeActions(edge: .leading) {
                        Button(task.isDone ? "Undo" : "Done") {
                            task.isDone.toggle()
                        }
                        .tint(.green)
                    }
            }
        }
        .refreshable {
            // Async data loading
            await loadTasks()
        }
    }
}

.refreshable — a modifier for iOS 15+ that adds pull-to-refresh. It accepts an async closure that executes when the user pulls the list down. SwiftUI automatically displays a loading indicator. After the operation completes, the indicator is hidden.

.onDelete and .onMove — modifiers for iOS 13+ that add support for row deletion and reordering. To use them, wrap the data in ForEach with Binding or pass closures through .onDelete(perform:) on List or ForEach.

List performance and optimization

List performance depends on the number of rows, the complexity of each cell, and the frequency of data updates. SwiftUI uses lazy-loading and cell reuse (similar to UITableView.dequeueReusableCell), but additional optimizations may be required for lists with 500+ rows.

OptimizationDescriptioniOS version
IdentifiableUnique IDs for each itemiOS 13+
EquatableViewAvoids redrawing when data is equaliOS 13+
id(_:)Forces View recreation when ID changesiOS 13+
.equatable()Strict Equatable comparisoniOS 15+
Diffable dataAutomatic diff on changesiOS 15+

Issue 1: Frequent updates. If the data in the list updates frequently (e.g., every second), List may redraw visible cells on each state change. Solution: use structures (value types) for data — SwiftUI compares them by value and redraws only changed rows.

Issue 2: Heavy cells. If each row contains a complex View hierarchy, images, and animations, scrolling may lag. Solution: extract cells into separate Views, use EquatableView to prevent unnecessary redraws. According to SwiftUI Lab (2024), splitting a complex row into subcomponents reduces rendering time by 30–50%.

Issue 3: Large number of rows. With 1000+ rows, List still works efficiently thanks to lazy-loading, but initial loading may slow down due to layout computation. Solution: use LazyVStack only for lists with uniform rows where List features (swipe, sections) are not needed. For full-featured lists, List remains the best choice.

Frequently Asked Questions

What is List in SwiftUI?

List is a container View for displaying a scrollable data list in SwiftUI. It is the equivalent of UITableView in UIKit with a declarative API. Supports sections, swipe actions, pull-to-refresh, editing, and customization via .listStyle().

How is List different from ScrollView + VStack?

List uses lazy-loading and cell reuse — cells are created as you scroll. ScrollView + VStack creates all Views at once. For lists with 50+ rows, List is preferable. For fixed small sets (up to 20 items), the difference is negligible.

How to add pull-to-refresh to List?

Use the .refreshable modifier (iOS 15+). Pass an async closure with data update logic. SwiftUI automatically displays a loading indicator and hides it after the async operation completes.

How to group rows in List?

Use Section View with a header and optional footer. Place list rows inside Section. For dynamic sections, use ForEach with groupedData. The list style is configured via .listStyle(.insetGrouped) for an iOS-like appearance.

How to speed up List with a large number of rows?

Use structures (value types) for data, extract complex cells into separate Views with EquatableView, avoid frequent state updates in each row. For lists with 1000+ rows, consider LazyVStack if List features are not needed.

Summary

  • List — container for a scrollable list with lazy-loading and cell reuse
  • ForEach — the primary way to dynamically display data in List
  • Section — grouping rows with headers and footer
  • swipeActions — swipe actions for iOS 15+
  • refreshable — pull-to-refresh for iOS 15+
  • Styles — insetGrouped, plain, inset, sidebar via .listStyle()
  • Performance — use Identifiable, EquatableView and value types

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also