🍎 Kuba Suder's blog on Mac & iOS development


Stacks, Grids, and Outlines in SwiftUI

Categories: SwiftUI 0 comments Watch the video


New lazy stack views: LazyVStack and LazyHStack

They work just like the existing horizontal and vertical stacks, but they render their content incrementally as it becomes visible

Useful for very long lists built using VStack/HStack that had performance issues previously

There’s no point using lazy stacks for small local stacks that just lay out single views that are all visible on the screen together

If you’re unsure, use standard HStack/VStack by default, and only use lazy stacks for long content that’s scrolling and becomes a performance bottleneck


New grid views: LazyHGrid and LazyVGrid

They lay out subviews in a grid like collection view

LazyVGrid(columns: columns, spacing: 0) { … }

Grids require a definition of columns:

Constant number of columns:

var columns = [
  GridItem(spacing: 0),
  GridItem(spacing: 0),
  GridItem(spacing: 0)

Adaptive columns, where the number of columns depends on the window size:

var columns = [
  GridItem(.adaptive(minimum: 300), spacing: 0)

Adaptive columns are useful e.g. in landscape mode on iPad or on macOS, where windows can be resized

Lists and outlines

Lists are more than just stacks of content – they support selection and scrolling etc.

Lists don’t need a Lazy variant, because list contents are always loaded lazily

Lists can now show hierarchical trees of items (outlines)

To make an outline list, provide a children keypath:

List(graphics, children: \.children) { graphic in

To make separate collapsible trees in each list section, use an OutlineGroup instead. An OutlineGroup is like ForEach with support for a hierarchical structure of items:

List {
    ForEach(canvases) { canvas in
        Section(header: Text( {
            OutlineGroup(items, children: \.children) { graphic in

You can also implement other views with parts that the user can collapse and expand like in an outline (e.g. to let them show or hide a section of a form with various controls inside) using the new DisclosureGroup control:

DisclosureGroup(isExpanded: $expanded) {
} label: {
  Label("Fill", systemImage: "rectangle.3.offgrid.fill")


DisclosureGroup("Fill") { HiddenContents() }

The binding passed in the isExpanded argument lets you keep track of and control whether the section is visible or not and e.g. show some sections as expanded by default when the view loads, or save the current state to user preferences

Leave a comment


This will only be used to display your Gravatar image.


What's the name of the base class of all AppKit and UIKit classes?