The UICollectionView is a wildly popular control in UIKit, developers have been hungry for a SwiftUI counterpart since it was introduced at WWDC19. Up until now we've been able to use combinations of VStack
and HStack
to achieve similar results, but known deep down that there's a better way!
SwiftUI 2 introduces LazyHGrid
and LazyVGrid
to bridge the gap between stacks in SwiftUI and UICollectionView
in UIKit, these grids will dynamically resize your SwiftUI views based on the layout parameters you provide.
You may be wondering why they're both marked with Lazy
, it's because of the support for lazy loading, meaning they won't load the contents until they are needed, you may be familiar the lazy keyword already in swift. SwiftUI 2 also introduces LazyVStack
and LazyHStack
based on the same principle.
At the time of writing this you'll need the latest beta of Xcode 12 to run this code.
GridItem
to work with the new grid types in SwiftUI you'll first need to understand GridItem
. One or more instances of GridItem
supply the layout parameters for grids in SwiftUI and can either represent a row or a column. The grid item constructor looks like this , init(GridItem.Size, spacing: CGFloat?, alignment: Alignment?)
where
GridItem.Size
is an enum that can be.adaptive
,.fixed
orflexible
- Spacing represents the distance between each view in the grid
- alignment is the layout alignment on either axes
To quickly elaborate on GridItem.Size
-
adaptive(minimum: CGFloat, maximum: CGFloat)
will place multiple views in the space of a single column or row. fixed(CGFloat)
will place a single view of a fixed width/height in the space of a single column or row.flexible(minimum: CGFloat, maximum: CGFloat)
will place a single view if flexible width/height in the space of a single column or row.
You can use multiple instances to achieve more complex layouts, for example if you want three columns in a LazyVGrid
you can provide an array of three .flexible()
grid items to the constructor. This will hopefully become clearer when we see some examples.
The LazyVGrid
and LazyHGrid
both rely on an array of GridItem
s to dictate their layout, the former using them for columns and the latter using them for rows. Let's take a look at this in practice starting with the LazyVGrid
.
var columns: [GridItem] =
[.init(.adaptive(minimum: 50, maximum: 100))]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 2) {
ForEach((0...100), id: \.self) { index in
RoundedRectangle(cornerRadius: 5)
.foregroundColor(.pink)
.frame(height: 50)
RoundedRectangle(cornerRadius: 5)
.foregroundColor(.blue)
.frame(height: 50)
}
}.font(.body)
}
}
Notice we're using the GridItem.Size.adaptive
, this will fill an entire row with Views using the minimum and maximum values for sizing. Below you can see how the LazyVGrid
looks when used with different combinations of GridItem.Size
.

Let's see what happens when we apply the same GridItem
s to LazyHGrid
.
var rows: [GridItem] =
[.init(.adaptive(minimum: 50, maximum: 100))]
var body: some View {
ScrollView(.horizontal) {
LazyHGrid(rows: rows, spacing: 2) {
ForEach((0...100), id: \.self) { index in
RoundedRectangle(cornerRadius: 5)
.foregroundColor(.pink)
.frame(width: 50)
RoundedRectangle(cornerRadius: 5)
.foregroundColor(.blue)
.frame(width: 50)
}
}.font(.body)
}
}
How has this code changed?
- Renamed
columns
torows
- Changed
LazyVGrid
toLazyHGrid
- Changed first parameter in grid initialiser from
columns
torows
- Gave
ScrollView
a layout direction of.horizontal
Let's see how this looks.

With those minor changes we've changed the layout from a vertically scrolled grid to a horizontally scrolled grid. Play around with different combinations of GridItem
layouts in your own code and see the results! It's a lot of fun.
Thanks for reading ✌️