SwiftUI: How to create LazyGrid with equal number of rows and columns?

Viewed 5493

I am trying to create a grid in SwiftUI 2 using the new primitives LazyVGrid/LazyHGrid.

My goal is to create a grid with the following properties:

  • Each cell has the same width as height (basically a square)
  • The grid has an equal number of rows and columns (Columns = Rows)
  • The content should not be scrollable and always fit the size of my view/screen.

Here's a video with the expected results. This was achieved by a custom Grid object that uses GeometryReader.

enter image description here

I've tried using .contentMode(1, .fit) to restrain the LazyVGrid to a 1:1 ratio but it doesn't seem to work. The red background represents the frame of the Grid which is not the expected behaviour.

LazyVGrid(columns: [GridItem(.adaptive(minimum: 32), spacing: 0)], spacing: 0) {
    ForEach(1...100, id: \.self) { item in
        Rectangle()
            .aspectRatio(1, contentMode: .fit)
            .border(Color.black)
    }
}
.aspectRatio(1, contentMode: .fit)
.background(Color.red)

Grid Attempt

I have also tried using GeometryReader to fix the size of the LazyVGrid, but it still didn't work. Any other ideas on how to achieve this?

3 Answers

Try the following (tested with Xcode 12b3 / iOS 14, no big-sur)

struct TestGridFitView: View {
    let columns = Array(repeating: GridItem(.flexible(minimum: 32), spacing: 0), count: 10)
    var body: some View {
        LazyVGrid(columns: columns, spacing: 0) {
            ForEach(1...100, id: \.self) { item in
                Rectangle().fill(Color.clear)
                    .aspectRatio(1, contentMode: .fit)
                    .border(Color.black)
            }
        }
        .background(Color.red)
        .aspectRatio(contentMode: .fit)
        
    }
}

After seeing @Asperi's solution above, I've managed to solve the final piece of the puzzle. Here's the code:

var body: some View {
    let columns = Array(repeating: GridItem(.flexible(minimum: 4, maximum: 48), spacing: 0), count: 10)

    LazyVGrid(columns: columns, spacing: 0) {
        ForEach(1...100, id: \.self) { item in
            Rectangle()
                .aspectRatio(1, contentMode: .fit)
                .border(Color.black)
        }
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
    .aspectRatio(1, contentMode: .fit)
    .background(Color.red)
}

Final Product

Have you tried giving an infinity max width? Like this:

    LazyVGrid(columns: [GridItem(.adaptive(minimum: 32), spacing: 0)], spacing: 0) {
    ForEach(1...100, id: \.self) { item in
        Rectangle()
            .aspectRatio(1, contentMode: .fit)
            .border(Color.black)
    }
}
.frame(maxWidth: .infinity)
.background(Color.red)
Related