NEW: Subscribe to Hacking with Swift+ and accelerate your learning! >>

How to position views in a grid using LazyVGrid and LazyHGrid

Paul Hudson    @twostraws   

Updated for Xcode 12.0

New in iOS 14

SwiftUI’s LazyVGrid and LazyHGrid give us grid layouts with a fair amount of flexibility, but they are available only for iOS 14 or later – I’m going to demonstrate them here, but if you need to support iOS 13 you should skip this part and look below for code that works on iOS 13.

The simplest possible grid is made up of three things: your raw data, an array of GridItem describing the layout you want, and either a LazyVGrid or a LazyHGrid that brings together your data and your layout.

For example, this will create a vertical grid layout using cells that are 80 points in size:

struct ContentView: View {
    let data = (1...1000).map { "Item \($0)" }

    let columns = [
        GridItem(.adaptive(minimum: 80))
    ]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(data, id: \.self) { item in
                    Text(item)
                }
            }
            .padding(.horizontal)
        }
    }
}

Using GridItem(.adaptive(minimum: 80)) means we want the grid to fit in as many items per row as possible, using a minimum size of 80 points each.

If you wanted to control the number of columns you can use .flexible() instead, which also lets you specify how big each item should be but now lets you control how many columns there are. For example, this will create two columns:

let columns = [
    GridItem(.flexible()),
    GridItem(.flexible())
]

And this would create five:

let columns = [
    GridItem(.flexible()),
    GridItem(.flexible()),
    GridItem(.flexible()),
    GridItem(.flexible()),
    GridItem(.flexible())
]

A third option is to use fixed sizes. For example, this will make the first column be exactly 100 points wide, and allow the second column to fill up all the remaining space:

let columns = [
    GridItem(.fixed(100)),
    GridItem(.flexible()),
]

You can also use LazyHGrid to make a horizontally scrolling grid, which works in much the same way except it accepts rows in its initializer.

For example, we could create 10 side by side heading images that are horizontally scrolling like this:

struct ContentView: View {
    let items = 1...10

    let rows = [
        GridItem(.fixed(200)),
    ]

    var body: some View {
        ScrollView(.horizontal) {
            LazyHGrid(rows: rows, alignment: .center) {
                ForEach(items, id: \.self) { item in
                    Image("Heading\(item)")
                }
            }
        }
    }
}

If you’re stuck using iOS 13, read below for the alternative…

SwiftUI gives us VStack for vertical layouts and HStack for horizontal layouts, but nothing that does both – nothing that can lay out views in a grid structure.

Fortunately we can write one ourselves by leveraging SwiftUI’s view builder system. This means writing a type that must be created using a row and column count, plus a closure it can run to retrieve the views for a given cell in the grid. Inside the body it can then loop over all the rows and columns and create cells inside VStack and HStack to make a grid, each time calling the view closure to ask what should be in the cell.

In code it looks like this:

struct GridStack<Content: View>: View {
    let rows: Int
    let columns: Int
    let content: (Int, Int) -> Content

    var body: some View {
        VStack {
            ForEach(0 ..< rows, id: \.self) { row in
                HStack {
                    ForEach(0 ..< self.columns, id: \.self) { column in
                        self.content(row, column)
                    }
                }
            }
        }
    }

    init(rows: Int, columns: Int, @ViewBuilder content: @escaping (Int, Int) -> Content) {
        self.rows = rows
        self.columns = columns
        self.content = content
    }
}

When you want to use that in a SwiftUI view, you’d write this:

GridStack(rows: 4, columns: 4) { row, col in
    Image(systemName: "\(row * 4 + col).circle")
    Text("R\(row) C\(col)")
}

That creates a 4x4 grid with an image and text in each cell.

Hacking with Swift is sponsored by Instabug

SPONSORED Are you tired of wasting time debugging your Swift app? Instabug’s SDK is here to help you minimize debugging time by providing you with complete device details, network logs, and reproduction steps with every bug report. All data is attached automatically, and it only takes a line of code to setup. Start your free trial now and get 3 months off exclusively for the Hacking with Swift Community.

Start your free trial!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Similar solutions…

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.7/5

Link copied to your pasteboard.