Swift and SwiftUI tutorials for iOS and Swift Developers - Swift Programming

LazyVStack vs List in SwiftUI

If you are an iOS Developer working in the Apple ecosystem, you know that performance and user experience are fundamental pillars in any application. Since the arrival of SwiftUI, the way we build interfaces has changed drastically compared to UIKit. However, with this new paradigm come new architectural doubts.

One of the most common and recurring debates when opening Xcode is choosing the right component to display data collections: List vs LazyVStack in SwiftUI.

At first glance, both seem to solve the same problem: rendering data iterations in a vertical column. However, under the hood, their behaviors, memory management, and design capabilities are radically different. In this article, we will break down what each one is, when you should use them, and how they behave across different platforms like iOS, macOS, and watchOS.


What is a List in SwiftUI?

The List is one of the oldest and most robust components in SwiftUI. If you come from the UIKit world, you can think of the List as the direct and modernized equivalent of UITableView.

A List is designed to display rows of data with a format standardized by the operating system. It is strongly tied to Apple’s Human Interface Guidelines, which means that, by default, it will provide you with margins, separators, backgrounds, and native scroll behaviors without you having to write additional code.

Main Features of the List

  1. Built-in Native Style: Lists come with predefined styles (.listStyle()) such as PlainListStyle, GroupedListStyle, or InsetGroupedListStyle.
  2. Automatic Memory Management: Lists in SwiftUI are inherently lazy. They only render views into memory that are visible on the screen (and a small buffer above and below).
  3. Out-of-the-box Features: It natively supports swipe actions (.swipeActions), edit mode for reordering or deleting rows (.onDelete, .onMove), and pull to refresh (.refreshable).

Code Example with List in Swift

import SwiftUI

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

struct ContactListView: View {
    @State private var contacts = [
        Contact(name: "Ana"), Contact(name: "Charles"), Contact(name: "Helen")
    ]

    var body: some View {
        NavigationView {
            List {
                ForEach(contacts) { contact in
                    Text(contact.name)
                        .swipeActions(edge: .trailing) {
                            Button(role: .destructive) {
                                // Delete action
                            } label: {
                                Label("Delete", systemImage: "trash")
                            }
                        }
                }
            }
            .navigationTitle("Contacts")
            .refreshable {
                // Logic to reload data
            }
        }
    }
}

As you can observe in this Swift code, with just a few lines we have implemented a list with swipe to delete and pull to refresh. This is the magic of the List.


What is a LazyVStack in SwiftUI?

Introduced in iOS 14, the LazyVStack (Lazy Vertical Stack) was born to solve the performance issues of the traditional VStack when handling large amounts of data, and the design rigidity of the List.

While a normal VStack instantiates and renders all its child elements immediately (which would crash the memory if you have 10,000 items), a LazyVStack lives up to its name: it creates elements only as they need to appear on screen.

Unlike the List, the LazyVStack is a completely blank canvas. It does not provide scrolling by itself (you must wrap it in a ScrollView), it has no separators, no default margins, and does not include native behaviors like swipe to delete.

Main Features of the LazyVStack

  1. Tailored Optimized Performance: Renders views on demand, making it ideal for infinite data feeds.
  2. Total Design Control: By not imposing system styles, you can create completely custom interfaces, cards, or complex grids.
  3. Modular Behavior: It works in conjunction with a ScrollView, allowing you to decide exactly how and where scrolling happens, and even add static elements within the same scroll that are not part of the lazy stack.

Code Example with LazyVStack in Swift

import SwiftUI

struct FeedView: View {
    let items = Array(1...1000)

    var body: some View {
        ScrollView {
            LazyVStack(spacing: 16, pinnedViews: [.sectionHeaders]) {
                Section(header: Text("My Custom Feed").font(.headline).background(Color.white)) {
                    ForEach(items, id: \.self) { item in
                        VStack(alignment: .leading) {
                            Text("Post #\(item)")
                                .font(.title3)
                                .fontWeight(.bold)
                            Text("This is a completely custom design that does not follow the rules of a standard List.")
                                .foregroundColor(.secondary)
                        }
                        .padding()
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .background(Color.blue.opacity(0.1))
                        .cornerRadius(12)
                        .padding(.horizontal)
                    }
                }
            }
        }
    }
}

In this scenario in Xcode, we have created a card design that would be very tedious to replicate inside a List due to the default styles it imposes.


Analyzing the Behavior Under the Hood

For a senior iOS Developer, the real difference lies in how the framework manages the lifecycle of the views.

When you use a List, SwiftUI greatly optimizes cell recycling at the operating system level. However, the List can sometimes be unpredictable with the internal state of its rows if they are very complex.

The LazyVStack, on the other hand, calls .onAppear and .onDisappear much more strictly as the user scrolls. This makes it ideal for triggering asynchronous network calls (like loading the next page from an API) exactly when the user reaches the end of the stack.

Performance Note: Although LazyVStack is efficient, if the views it contains are computationally heavy to initialize, you might notice slight “stutters” when scrolling fast. The List usually handles ultra-fast scrolling a bit better thanks to the underlying UIKit inheritance.


Comparison Table: List vs LazyVStack

Below, we present a summary table so you have a quick reference in your day-to-day Swift programming:

FeatureListLazyVStack (+ ScrollView)
Default DesignApple system styles (separators, margins).None. Total blank canvas.
Built-in ScrollYes, no need for ScrollView.No, must be inside a ScrollView.
Swipe ActionsNative (with .swipeActions modifier).Not supported natively (requires complex code).
Pull to RefreshNative (with .refreshable modifier).Native in iOS 15+ applied to the ScrollView.
Edit Mode / ReorderNative (with EditButton and .onMove).Not supported natively.
Row SeparatorsAutomatic (can be hidden with listRowSeparator).Manual (you must draw a Divider yourself).
Performance in huge listsExcellent (optimized cell recycling).Very good (on-demand instantiation).
UI FlexibilityRigid. Hard to override backgrounds and margins.Absolute. You own every pixel.

When to use each component in App Development

The decision between List vs LazyVStack in SwiftUI should be based purely on the user experience and design requirements of your application.

You should use List when:

  1. You are building configuration or settings screens. (Example: the iOS Settings app). The .listStyle(.insetGrouped) style is designed exactly for this.
  2. You need standard system interactions. If your app requires the user to swipe to delete, reorder elements by dragging them, or select multiple rows, the List will save you hundreds of lines of code and headaches.
  3. The design dictates a native Apple “Look and Feel”. Many users prefer the familiarity of native components. If you don’t have a design team pushing for a radically different interface, go with the List.

You should use LazyVStack when:

  1. The design is completely custom. If you have Figma or Sketch designs featuring cards with complex shadows, asymmetrical indents, or dynamic backgrounds without line separators, fighting against the List will be frustrating. The LazyVStack gives you total freedom.
  2. You have a social media style content feed. Think of Instagram or Twitter. These are not “lists” in the sense of the Settings app; they are continuous streams of varied content. A LazyVStack inside a ScrollView is the standard architecture for this.
  3. You need sections with horizontal scrolling within vertical scrolling. Combining a vertical LazyVStack that in turn contains a ScrollView(.horizontal) (with a LazyHStack) works infinitely better and with fewer layout bugs than trying to fit this inside the cells of a List.

Cross-Platform Considerations (iOS, macOS, watchOS)

One of the great advantages of SwiftUI is its portability across the Apple ecosystem. However, the behavior of these components varies depending on the device.

On iOS and iPadOS

On iPadOS, the List shines especially when used in side navigation architectures (NavigationSplitView). If you apply a .listStyle(.sidebar), SwiftUI will automatically transform your list into a side navigation bar with a translucent style and native collapsible behavior. Trying to replicate an iPadOS sidebar with a LazyVStack is a waste of time.

On macOS

In Mac development using Xcode, lists acquire desktop selection behaviors. A List in macOS natively supports multiple row selection using the Shift or Command key. Furthermore, table styles (.listStyle(.inset)) adopt the macOS aesthetic (like Finder or Mail). The LazyVStack, on the other hand, remains useful for custom galleries or grids, but it loses all the desktop accessibility features that the List gives you for free.

On watchOS

The Apple Watch has extremely limited resources. On watchOS, the List is almost mandatory for any main menu or navigation. SwiftUI optimizes lists on the watch so they interact perfectly with the Digital Crown. Although you can use a ScrollView with a LazyVStack, the OS handles focus and haptic feedback much better when you use a standard List.


Final Thought for the iOS Developer

As you advance in your Swift programming career, you will realize that there is no silver bullet. The dilemma of List vs LazyVStack in SwiftUI is not about which is better, but rather which is the right tool for the specific job.

As a general rule, adopt a pragmatic approach: Always start with a List. It will provide you with accessibility, performance, and expected user behavior for “free”. Only when the design requirements of your app (UI/UX) start violently clashing against the constraints of the List (and you find yourself fighting with modifiers like .listRowInsets, .listRowBackground, etc.), is it time to refactor your code and migrate to a ScrollView combined with a LazyVStack.

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Article

Format Numbers in SwiftUI

Next Article

SwiftUI Default Scroll Anchor

Related Posts