LazyVGrid and LazyHGrid in SwiftUI iOS 03.06.2021

With the introduction of iOS 14, SwiftUI includes three views for the purpose of displaying multicolumn grids within a user interface layout in the form of LazyVGrid, LazyHGrid and GridItem.

LazyVGrid and LazyHGrid views only create items to be displayed within the grid when they are about to become visible to the user and then discards those items from memory as they scroll out of view. This allows scrollable grids of potentially infinite numbers of items to be constructed without adversely impacting app performance.

The syntax for declaring a vertical grid is as follows:

LazyVGrid(columns: [GridItem], alignment: <horizontal alignment>, spacing: CGFloat?, pinnedViews: <views>) {
    // Content Views
}

In the above syntax, only the columns argument is mandatory and takes the form of an array of GridItem instances.

Each row or column in a grid layout is represented by an instance of the GridItem view. In other words, a GridItem instance represents each row in a LazyHGrid layout and each column when using the LazyVGrid view. The GridItem view also provides control over the number of rows or columns displayed within a grid and the minimum size to which an item may be reduced to meet those constraints.

GridItems are declared using the following syntax:

GridItem(sizing, spacing: CGFloat?, alignment: <alignment>)

The sizing argument is of type GridItemSize and must be declared as one of the following:

  • flexible(). The number of rows or columns in the grid will be dictated by the number of GridItem instances in the array passed to LazyVGrid or LazyHGrid view.
  • adaptive(minimum: CGFloat). The size of the row or column is adjusted to fit as many items as possible into the available space. The minimum size to which the item may be reduced can be specified using the optional minimum argument.
  • fixed(size: CGFloat). Specifies a fixed size for the item.

Flexible GridItems

struct ContentView: View {    
    private var colors: [Color] = [.blue, .yellow, .green]
    private var gridItems = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]

    struct CellContent: View {
        var index: Int

        var color: Color
        var body: some View {
            Text("\(index)")
                .frame(minWidth: 50, maxWidth: .infinity, minHeight: 100)
                .background(color)
                .cornerRadius(8)
                .font(.system(.largeTitle))
        }
    }

    var body: some View {
        LazyVGrid(columns: gridItems, spacing: 5) {
            ForEach((0...8), id: \.self) { index in
                CellContent(index: index, color: colors[index % colors.count])
            }
        }
        .padding(5)
    }
}

Adaptive GridItems

The adaptive setting configures the grid view to automatically display as many rows or columns as it can fit into the space occupied by the view. To use adaptive sizing, modify the gridItems array to contain a single adaptive item as follows:

private var gridItems = [GridItem(.adaptive(minimum: 50))]

This change will result in the grid displaying as many columns as possible with the restriction that the column width cannot be less than 50dp.

Fixed GridItems

The GridItem fixed size setting allows rows or columns to be set at a specific size. When using only fixed GridItems in the array passed to the grid view, the number of GridItems will dictate the number of rows or columns. For example, the following array, when passed to a LazyVGrid view, will display a grid containing a single column with a width of 100dp.

private var gridItems = [GridItem(.fixed(100))]

The following array, on the other hand, will display a three column grid with the columns sized at 75dp, 125dp and 175dp respectively:

private var gridItems = [GridItem(.fixed(75)), GridItem(.fixed(125)), GridItem(.fixed(175))]

When working with grids it is also possible to combine GridItem sizing configurations. The following array, for example, will display the first column of each row with a fixed width with the second and third columns sized equally to occupy the remaining space:

private var gridItems = [GridItem(.fixed(85)), GridItem(), GridItem()]

Using the LazyHGrid View

Horizontal grids work in much the same way as vertically oriented grids with the exception that the configuration is based on rows instead of columns, and that the fixed, minimum and maximum values relate to row height instead of column width. Also, when scrolling is required the grid should be embedded in a horizontal ScrollView instance. The following declaration, for example, places a LazyHGrid within a horizontal ScrollView using adaptive sizing on all rows:

struct ContentView: View {
    private var colors: [Color] = [.blue, .yellow, .green]
    private var gridItems = [GridItem(.adaptive(minimum: 50))]

    struct CellContent: View {
        var index: Int

        var color: Color
        var body: some View {
            Text("\(index)")
                .frame(minWidth: 75, minHeight: 50, maxHeight: .infinity)
                .background(color)
                .cornerRadius(8)
                .font(.system(.largeTitle))
        }
    }

    var body: some View {
        ScrollView(.horizontal) {
            LazyHGrid(rows: gridItems, spacing: 5) {
                ForEach((0...88), id: \.self) { index in
                    CellContent(index: index, color: colors[index % colors.count])
                }
            }
            .padding(5)
        }
    }
}