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