WWDC 20
Stacks, Grids, and Outlines in SwiftUI
Stacks
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
Grids
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 GraphicRow(graphic) }
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(canvas.name)) { OutlineGroup(items, children: \.children) { graphic in GraphicRow(graphic) } } } }
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) { HiddenContents() } label: { Label("Fill", systemImage: "rectangle.3.offgrid.fill") }
or:
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