SwiftUI: Layout & Interfaces

Nov 18 2021 Swift 5.5, iOS 15, Xcode 13

Part 1: Dynamic View Layout

5. Grids

Episode complete

Play next episode

Next
Save for later
About this episode
See versions

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 4. Challenge: Nested Scrolling Stacks Next episode: 6. PinnedScrollableViews

This video Grids was last updated on Nov 18 2021

By this point, you’ve gotten a good handle on all of SwiftUI’s Stacks. Lazy, or not Lazy… …Horizontal, or Vertical. And you know how to nest both dimensions of Stacks within each other.

In this episode, you’re going to learn about an even more powerful tool for layouts that require more two-dimensional complexity: grids.

SwiftUI offers two types of grids: ones that expand horizontally: LazyHStacks, and ones that expand vertically: LazyVStacks. Grids only come in lazy variants. I think a good way to learn about grids is to start off with a stack, and convert it. Let’s do that now.

Here, we’ve got a LazyVStack with 20 random subgenres from a random genre. You can almost make the switch we’re going for, by changing this “Stack” to “Grid”.

LazyVGrid {

Use the fix-it there, and you see that we’ll need a Grid Item Array, to define some columns.

LazyVGrid(columns: <#[GridItem]#>) {

Let’s start off by initializing a single Grid Item.

    LazyVGrid(
      columns: [
        .init()
      ]
    ) {

And that looks exactly the same as the VStack! Option-click on that init, and you’ll see that we’re using the default “GridItem Size” of “flexible”. Let’s add two more grid items that use flexible sizing.

      columns: [
        .init(),
        .init(),
        .init()
      ]

There we go. Now, with multiple columns, it looks like a grid. Flexible sizing gives all the columns the same width.

But there are other options. One of them is “fixed” size. Which will just enforce a certain point value.

      columns: [
        .init(.fixed(50)),
        .init(),

And if you manually choose flexible

.init(.fixed(50)),
.init(.flexible()),
.init()

You can give it minimum and maximum settings.

        .init(.fixed(50)),
        .init(.flexible(minimum: 200)),
        .init()

The last case of Grid Size is “adaptive”.

.init(.adaptive(minimum: <#T##CGFloat#>, maximum: <#T##CGFloat#>))

It also has minimum and maximum settings, but they’re not just for a single visual column! If you supply it with a large enough minimum value, the result will be exactly the same as the “flexible option”.

.init(.adaptive(minimum: 100))

But if has enough room to fit multiple columns of that size, in a given space…

        .init(.flexible(maximum: 100)),
        .init(.adaptive(minimum: 30))

…then, you’ll get as many items as possible, all of the same width, in that space. So actually, when using “adaptive”, you may not need to define multiple columns.

//        .init(.fixed(50)),
//        .init(.flexible(maximum: 100)),
        .init(.adaptive(minimum: 30))

Whatever you need, a combination of these three options should have you covered.

GridItems also offer a “spacing” argument, which applies internally to their grid.

        .init(.fixed(50), spacing: 30),
        .init(.flexible(maximum: 100), spacing: 30),
        .init(.adaptive(minimum: 30), spacing: 30)

To switch axes, it’s as simple as changing the V to an H, just like with Stacks…

    LazyHGrid(

…and changing “columns” to “rows”.

    LazyHGrid(
      rows: [

And if you’re fixing the height of grid children views, like we are here…

      .frame(height: 125)

…you’ll probably want to switch to fixing width, instead.

.frame(width: 125)

And, be aware, Grids will grow to whatever size they need to, to accommodate all of their children, based on the grid items you supply them.

      rows: [
        .init(.fixed(300), spacing: 30),
        .init(.flexible(maximum: 200), spacing: 30),
        .init(.adaptive(minimum: 50), spacing: 30)
      ]

You can see the extent of the Grid, in blue, in the preview, but without a scroll view, there’s no way to get to them all.

  var body: some View {
    ScrollView(.horizontal) {
      LazyHGrid(
        rows: [
          .init(.fixed(300), spacing: 30),
          .init(.flexible(maximum: 200), spacing: 30),
          .init(.adaptive(minimum: 50), spacing: 30)
        ]
      ) {
        ForEach(
          Genre.list.randomElement()!.subgenres.shuffled().prefix(20),
          content: \.view
        )
      }
      .padding(.horizontal)
    }
  }

So use a scroll view when you know that your grid might not fit!

Genre.list.randomElement()!.subgenres,