LazyVGrid & LazyHGrid
Adding a list of items in a grid layout.
LazyVGrid & LazyHGrid
Adding a list of items in a grid layout.
0
0
Checkbox to mark video as read
Mark as read

SwiftUI provides powerful tools to create grid-based layouts with LazyVGrid (Vertical) and LazyHGrid (Horizontal). These components allow us to build efficient, flexible, and responsive grid layouts in vertical and horizontal orientations.

Define the grid layout using GridItem

The GridItem struct is used to specify the layout of the grid cells. You can define the size and spacing of each column (LazyVGrid) or row (LazyHGrid). Here’s how to create a simple grid layout:

// Define a flexible grid with three columns
let columns = [
    GridItem(.flexible()),
    GridItem(.flexible()),
    GridItem(.flexible())
]

GridItem can have three types:

  • .fixed: Sets a fixed size for each item.
  • .flexible: Allows items to adjust their size to fill available space.
  • .adaptive: Adjusts the number of items to fit within a specified range.

Build a grid using LazyVGrid or LazyHGrid

Let's create an example where we use the 3 types of columns:

import SwiftUI

struct ContentView: View {

    let columns = [
        GridItem(.fixed(100)),
        GridItem(.flexible()),
        GridItem(.adaptive(minimum: 100, maximum: 100))
    ]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 16) {
                ForEach(1...30, id: \.self) { item in
                    Text("Item \(item)")
                        .frame(height: 100)
                        .frame(maxWidth: .infinity)
                        .background(Color.blue)
                        .cornerRadius(8)
                        .foregroundColor(.white)
                }
            }
            .padding()
        }
    }
}

#Preview {
    ContentView()
}

We've used .frame(maxWidth: .infinity) just to visualize the size of each column and how LazyVGrid can manage items size in order to conform the layout we defined.


The result is what it seems a 6 columns layout, but this is actually because the column 3 is adding all the items with the specific width that can fit.

Custom Layouts

Let's see an example of how to do a custom grid that will look like a honeycomb.


First, let's define an Hexagon shape that we'll use in our items. We have borrowed this code from https://calebhearth.com.

struct Hexagon: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let radius = min(rect.size.height, rect.size.width) / 2
        let corners = corners(center: center, radius: radius)
        path.move(to: corners[0])
        corners[1...5].forEach() { point in
            path.addLine(to: point)
        }
        path.closeSubpath()
        return path
    }

    func corners(center: CGPoint, radius: CGFloat) -> [CGPoint] {
        var points: [CGPoint] = []
        for i in (0...5) {
          let angle = CGFloat.pi / 3 * CGFloat(i)
          let point = CGPoint(
            x: center.x + radius * cos(angle),
            y: center.y + radius * sin(angle)
          )
          points.append(point)
        }
        return points
    }
}

In our main view, we are going to define the size of the items (100) and a layout of 10 fixed columns with the same size. This way we can use these values instead of adding random numbers directly to our views.

let numberOfColumns: Int = 10
let itemSize: CGSize = CGSize(width: 100, height: 100)

var columns: [GridItem] {
    Array(
        repeating: GridItem(.fixed(itemSize.width), spacing: 0),
        count: numberOfColumns
    )
}

Then, we'll apply our new Hexagon shape to our items, and apply an offset to the items depending on the column they are.

.clipShape(Hexagon())
.offset(y: yOffset(item: item))

func yOffset(item: Int) -> CGFloat {
    item % 2 == 0
    ? -(itemSize.width * 0.25)
    : (itemSize.width * 0.25)
}

Code

Here you have all the code together:

import SwiftUI

struct ContentView: View {

    let numberOfColumns: Int = 10
    let itemSize: CGSize = CGSize(width: 100, height: 100)

    var columns: [GridItem] {
        Array(
            repeating: GridItem(.fixed(itemSize.width), spacing: 0),
            count: numberOfColumns
        )
    }

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 0) {
                ForEach(1...100, id: \.self) { item in
                    Text("Item \(item)")
                        .frame(width: itemSize.width, height: itemSize.height)
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .clipShape(Hexagon())
                        .offset(y: yOffset(item: item))
                }
            }
            .padding()
        }
    }

    func yOffset(item: Int) -> CGFloat {
        item % 2 == 0
        ? -(itemSize.width * 0.25)
        : (itemSize.width * 0.25)
    }
}

struct Hexagon: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let radius = min(rect.size.height, rect.size.width) / 2
        let corners = corners(center: center, radius: radius)
        path.move(to: corners[0])
        corners[1...5].forEach() { point in
            path.addLine(to: point)
        }
        path.closeSubpath()
        return path
    }

    func corners(center: CGPoint, radius: CGFloat) -> [CGPoint] {
        var points: [CGPoint] = []
        for i in (0...5) {
          let angle = CGFloat.pi / 3 * CGFloat(i)
          let point = CGPoint(
            x: center.x + radius * cos(angle),
            y: center.y + radius * sin(angle)
          )
          points.append(point)
        }
        return points
    }
}

#Preview {
    ContentView()
}

0 Comments

Join the community to comment
Sign Up
I have an account
Be the first to comment

Accept Cookies

We use cookies to collect and analyze information on site performance and usage, in order to provide you with better service.

Check our Privacy Policy