LazyVGrid & LazyHGrid Overview

Tuesday, 09 March 2021
Updated 2 years ago
This is a new layout, but it is still in BETA.
I will start sending email newsletters once in a while, so in order to get notified when I release new content, make sure to follow me on twitter @liquidcoder .

Introduction

Apple says: "Container views that arrange its child views in a grid that grows vertically or horizontally respectively, creating items only as needed."

Creating views as needed means that only views that are a visible or are about to be visible will be created, so if you are scrolling through a long list of items, views will not be created at the same, only the ones that need to be display at that particular time.

LazyVGrid and LazyHGrid will eventually replace UIKit's UICollectionView, but for now they are still not as powerful.

GridItems

Apple says: "A description of a single grid item, such as a row or a column."

GridItems describe how items of a LazyVGrid or LazyHGrid will be laid out and which size each of them will have.

// $$liquidcoderID-LazyVHGrid1

init(GridItem.Size, spacing: CGFloat?, alignment: Alignment?)

Fixed Size GridItem With LazyVGrid

Fixed gridItems (columns) will keep the same size no matter how many of them you add in a grid which will make them overflow. Use fixed items if you know that the size of the content inside them will stay constant.

LazyVGrid uses HorizontalAlignment meaning they can only be aligned along the x-axis as you can see in the preview.

Note: Using a LazyVGrid makes the 100 value in the .fixed size be the width, hence the need to set the height on the Rectangle inside the ForEach.

// $$liquidcoderFilename-ContentView

import SwiftUI

extension HorizontalAlignment {

    ...
}

struct ContentView: View {

    // highlight-start
    @State var columns: [GridItem] = [GridItem(.fixed(100))]
    @State var alignment: HorizontalAlignment = .center
    // highlight-end

    var body: some View {
        VStack {
            HStack { ... }

            // highlight-start
            ScrollView {
                LazyVGrid(columns: columns, alignment: alignment) {
                    ForEach((0..<79), id: \.self) { i in
                        Rectangle()
                            .frame(height: 100)
                    }
                }.font(.largeTitle)
            }
            // highlight-end

            HStack { ... }
        }
    }
}

https://res.cloudinary.com/liquidcoder/image/upload/v1614684998/Blog/LazyVHGridOverview/prvgzsgyk83yhweaun3t.gif

Fixed Size GridItem With LazyHGrid

LazyHGrid uses VerticalAlignment meaning they can only be aligned along the y-axis as you can see in the preview.

Note: Using a LazyHGrid makes the 100 value in the .fixed size be the height, hence the need to set the width on the Rectangle inside the ForEach.

This is the opposite of what we have done with the LazyVGrid

// $$liquidcoderFilename-ContentView

import SwiftUI

extension VerticalAlignment {

    ...
}

struct ContentView: View {

    // highlight-start
    @State var rows: [GridItem] = [GridItem(.fixed(100)) ]
    @State var alignment: VerticalAlignment = .center
    // highlight-end

    var body: some View {
        VStack {
            HStack { ... }

            // highlight-start
            ScrollView(.horizontal) {
                LazyHGrid(rows: rows, alignment: alignment) {
                    ForEach((0..<79), id: \.self) { i in
                        Rectangle()
                            .frame(width: 100)
                    }
                }.font(.largeTitle)
            }
            // highlight-end

            HStack { ... }
        }
    }
}

https://res.cloudinary.com/liquidcoder/image/upload/v1614688245/Blog/LazyVHGridOverview/wy4urj4cxkpngakblu47.gif

Flexible Size GridItem

Flexible size is the same as fixed in such a way that the number of rows or columns is equal to the count of GridItem array. However, the width of each column or height of each row will fill the available space, unless you specify a minimum and maximum thresholds.

// $$liquidcoderID-LazyVHGrid5

case flexible(minimum: CGFloat = 10, maximum: CGFloat = .infinity)

As you can see when we only have 1 column, each item fill the entire width, but as soon as we start adding grid items to the array, the entire width is divided equally to accommodate each item.

Use this size if you know exactly how many rows or columns you will have.

// $$liquidcoderID-LazyVHGrid6
// $$liquidcoderFilename-ContentView

struct ContentView: View {

    // highlight-start
    @State var columns: [GridItem] = [GridItem(.flexible())]
    // highlight-end

    var body: some View {
        VStack {
            HStack {
                Text("# of gridItems: ")
                    + Text("\(columns.count)")
                    .bold()
                    .foregroundColor(.blue)

            }
            // highlight-start
            ScrollView {
                LazyVGrid(columns: columns) {
                    ForEach((0..<100), id: \.self) { i in
                        Rectangle()
                            .foregroundColor( i.isMultiple(of: 2) ? .blue : .yellow)
                    }
                }.font(.largeTitle)
            }
            // highlight-end
            HStack {
                Button(action: {
                    columns.append(GridItem(.flexible()))
                }, label: {
                    Text("Add Fixed GridItem")
                            .padding()
                            .background(Color.blue)
                            .foregroundColor(.white)
                })
            }
        }
    }
}

https://res.cloudinary.com/liquidcoder/image/upload/v1614770711/Blog/LazyVHGridOverview/x5fki1blptsljsua53ze.gif

Adaptive Size GridItem

With adaptive size, you don't need mulitple gridItems to have multiple columns or rows, you just need an array with a single item, and play with the minimum and maximum values.

The grid will create as many columns or rows depending if it's a LazyVGrid or a LazyHGrid respectively as possible, and the width of each column or height of each row will depend on the content that each column and row contains.

// $$liquidcoderID-LazyVHGrid8
// $$liquidcoderFilename-ContentView

import SwiftUI

struct ContentView: View {

    // highlight-start
    @State var columns: [GridItem] = [GridItem(.adaptive(minimum: 10))]
    // highlight-end

    // highlight-start
    @State private var min = 10
    // highlight-end

    var body: some View {
        VStack {
            HStack {
                Text("Minimum ")
                    + Text("\(min)")
                    .bold()
                    .foregroundColor(.blue)

            }
            // highlight-start
            ScrollView {
                LazyVGrid(columns: columns) {
                    ForEach((0..<100), id: \.self) { i in
                        Rectangle()
                            .foregroundColor( i.isMultiple(of: 2) ? .blue : .yellow)
                    }
                }.font(.largeTitle)
            }
            // highlight-end
            HStack {
                Button(action: {
                    min += 10
                    columns = [GridItem(.adaptive(minimum: CGFloat(min)))]
                }, label: {
                    Text("Add Fixed GridItem")
                            .padding()
                            .background(Color.blue)
                            .foregroundColor(.white)
                })
            }
        }
    }
}

https://res.cloudinary.com/liquidcoder/image/upload/v1614877268/Blog/LazyVHGridOverview/pyic8xmos21squrwvdhb.gif

Mixing sizes

You can also mix multiple different sizes to fit your needs.

// $$liquidcoderID-LazyVHGrid9
// $$liquidcoderFilename-ContentView

import SwiftUI

struct ContentView: View {

    // highlight-start
    var columns: [GridItem] = [GridItem(.adaptive(minimum: 30)), GridItem(.fixed(70)), GridItem(.flexible())]
    // highlight-end

    var body: some View {
        VStack {
            // highlight-start
            ScrollView {
                LazyVGrid(columns: columns) {
                    ForEach((0..<100), id: \.self) { i in
                        Rectangle()
                            .foregroundColor( i.isMultiple(of: 2) ? .blue : .yellow)
                    }
                }.font(.largeTitle)
            }
            // highlight-end
        }
    }
}

https://res.cloudinary.com/liquidcoder/image/upload/v1615278045/Blog/LazyVHGridOverview/bkao6xwubufwbn25n1rc.png

Grids in swiftUI are very powerful seeing that it's still only their first iteration which makes me wonder what type of magic will we get from them in future versions of swiftUI? The only thing I can say is that the future brighter guys. Happy coding!!!