How to Create Efficient Lazy Loading Stacks in SwiftUI ScrollView: Patterns and Examples

Use LazyVStack, LazyHStack, or LazyVGrid inside a ScrollView to render views on demand, and wrap the hierarchy in ScrollViewReader to enable programmatic scrolling without materializing off-screen items.

The Dimillian/Skills repository demonstrates production-ready patterns for handling large datasets in SwiftUI without memory bottlenecks. By leveraging lazy loading containers alongside ScrollViewReader and safeAreaInset, you can build scrollable interfaces that maintain 60fps performance with thousands of items. This guide extracts the implementation details from swiftui-ui-patterns/references/scrollview.md to show you how to construct these efficient stacks.

Why Lazy Stacks Improve ScrollView Performance

Standard VStack and HStack instantiate all child views immediately, creating massive view hierarchies that drain memory and CPU. According to the reference implementation in swiftui-ui-patterns/references/scrollview.md, lazy containers defer view creation until items intersect the visible viewport.

Memory efficiency comes from maintaining only the visible subset of views in memory. Layout performance improves because the engine performs layout passes only for rendered items, deferring calculations for off-screen content. Scrolling stability requires stable identity keys—using .id(message.id) prevents reuse bugs and ensures the scroll position remains accurate after data updates.

Building a Vertical Chat Feed with LazyVStack

Basic Implementation

For vertical content like message feeds or social timelines, wrap a LazyVStack inside a ScrollView. This pattern ensures that only the messages visible on screen consume rendering resources.

@MainActor
struct ConversationView: View {
    private enum Constants { static let bottomAnchor = "bottom" }
    @State private var scrollProxy: ScrollViewProxy?

    var body: some View {
        ScrollViewReader { proxy in
            ScrollView {
                LazyVStack {
                    ForEach(messages) { message in
                        MessageRow(message: message)
                            .id(message.id)
                    }
                    Color.clear.frame(height: 1).id(Constants.bottomAnchor)
                }
                .padding(.horizontal, .layoutPadding)
            }
            .safeAreaInset(edge: .bottom) {
                MessageInputBar()
            }
            .onAppear {
                scrollProxy = proxy
                withAnimation {
                    proxy.scrollTo(Constants.bottomAnchor, anchor: .bottom)
                }
            }
        }
    }
}

Key implementation details from swiftui-ui-patterns/references/scrollview.md:

  • Assign stable IDs using .id(message.id) to prevent view recycling issues
  • Add an invisible anchor view (Color.clear) with a unique ID to enable scrolling to the bottom
  • Store the ScrollViewProxy in state to access it outside the ScrollViewReader closure

Programmatic Scrolling with ScrollViewReader

The ScrollViewReader provides a ScrollViewProxy that locates items by their identity without materializing the entire list. Use proxy.scrollTo(_:anchor:) with animation to jump to specific items while maintaining lazy loading benefits.

Horizontal Layouts and Adaptive Grids

LazyHStack for Horizontal Chips

For horizontally scrolling content like category filters or avatar strips, use LazyHStack to load chips only when they approach the visible area.

ScrollView(.horizontal, showsIndicators: false) {
    LazyHStack(spacing: 8) {
        ForEach(chips) { chip in
            ChipView(chip: chip)
        }
    }
}

LazyVGrid for Media Grids

When displaying collections in a grid format, combine LazyVGrid with adaptive column sizing to maintain efficiency across different device sizes.

let columns = [GridItem(.adaptive(minimum: 120))]

ScrollView {
    LazyVGrid(columns: columns, spacing: 8) {
        ForEach(items) { item in
            GridItemView(item: item)
        }
    }
    .padding(8)
}

Advanced Production Patterns

Combining safeAreaInset with Lazy Stacks

To keep input bars or action buttons visible above the keyboard while maintaining lazy loading, attach them using safeAreaInset(edge:) rather than placing them inside the ScrollView content.

ScrollView {
    LazyVStack { /* content rows */ }
}
.safeAreaInset(edge: .bottom) {
    MessageInputBar()
}

This pattern, documented in swiftui-ui-patterns/references/scrollview.md, ensures the inset view stays fixed while the lazy stack scrolls beneath it.

Scroll-to-Top Functionality

Implement global navigation buttons by capturing the ScrollViewProxy and invoking scrollTo with animation:

struct FeedView: View {
    @State private var proxy: ScrollViewProxy?

    var body: some View {
        VStack {
            ScrollViewReader { p in
                ScrollView {
                    LazyVStack {
                        // Content rows with IDs assigned
                    }
                }
                .onAppear { proxy = p }
            }

            Button("Back to Top") {
                withAnimation {
                    proxy?.scrollTo(firstItemID, anchor: .top)
                }
            }
        }
    }
}

Performance Best Practices

  • Prefer lazy stacks for large or unknown item counts. Static content with fewer than 20 items does not benefit from lazy loading and may incur unnecessary overhead.
  • Maintain stable identities. Always provide explicit .id() values based on model identifiers, not index paths, to prevent view state loss during scrolling.
  • Avoid nesting scroll views of the same axis. Embedding a LazyHStack inside a vertical ScrollView is safe, but nesting two vertical scroll containers causes gesture conflicts and breaks lazy loading optimizations.
  • Defer heavy initialization. Move network requests and image loading into onAppear inside row views rather than the parent stack.

Summary

  • Wrap content in ScrollView and use LazyVStack, LazyHStack, or LazyVGrid to instantiate views only when they enter the visible viewport.
  • Implement programmatic scrolling with ScrollViewReader and stable IDs to avoid materializing off-screen items when jumping to specific locations.
  • Attach fixed UI elements like input bars using safeAreaInset(edge:) rather than including them in the scrollable content.
  • Reference swiftui-ui-patterns/references/scrollview.md in the Dimillian/Skills repository for the complete implementation patterns and pitfalls to avoid.

Frequently Asked Questions

How does lazy loading reduce memory usage in SwiftUI?

Lazy loading containers only instantiate views that intersect the visible viewport. Off-screen rows remain as lightweight data structures rather than fully materialized view hierarchies, dramatically reducing memory consumption when displaying thousands of items. As implemented in swiftui-ui-patterns/references/scrollview.md, this pattern defers both view creation and layout calculations until items are actually needed.

When should I use ScrollViewReader versus a standard List?

Use ScrollViewReader when you need precise programmatic control over scroll position, such as jumping to specific messages in a chat or implementing scroll-to-top buttons. While List provides built-in scrolling and cell reuse, it offers less control over scroll animations and positioning. ScrollViewReader works with any ScrollView content, including custom lazy stacks that List cannot accommodate.

What are the pitfalls of using lazy stacks for small static content?

Lazy stacks incur overhead from the lazy loading mechanism itself. For static content with fewer than 20 items, the deferred initialization provides no memory benefit and may actually reduce performance slightly. Additionally, lazy stacks can introduce layout flicker if item heights vary significantly, as the system must continuously adjust the content size when new items load.

How do I keep UI elements visible above the keyboard in a lazy stack?

Attach fixed elements using the safeAreaInset(edge:) modifier on the ScrollView rather than placing them inside the LazyVStack. This positions the view outside the scrollable area while automatically adjusting for keyboard presentation. The reference implementation in swiftui-ui-patterns/references/scrollview.md demonstrates this pattern for chat input bars that remain fixed at the bottom of the screen.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →