SwiftUI の LazyVGrid・LazyHGrid でグリッドレイアウトを作る

LazyVGrid と LazyHGrid は、スクロール可能なグリッドレイアウトを構築するためのコンテナです。「Lazy」の名前が示すとおり、画面に表示されるタイミングで初めてビューを生成するため、大量のアイテムを並べてもパフォーマンスが維持されます。

LazyVGrid の基本

LazyVGrid は縦スクロールのグリッドを作ります。列の定義には GridItem の配列を使います。

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

ScrollView {
    LazyVGrid(columns: columns, spacing: 16) {
        ForEach(0..<20) { index in
            RoundedRectangle(cornerRadius: 8)
                .fill(Color.blue.opacity(0.3))
                .frame(height: 100)
                .overlay(Text("\(index)"))
        }
    }
    .padding()
}

3 列の均等グリッドが縦スクロールで表示されます。columns に指定した GridItem の数がそのまま列数になるため、直感的にレイアウトを構成できます。

GridItem の 3 つのサイズタイプ

GridItem はサイズの決め方によって 3 種類に分かれます。この使い分けがグリッドレイアウトの柔軟性を支えています。

flexible

利用可能なスペースを均等に分配する。最も一般的なタイプで、レスポンシブなグリッドに適している。min と max で範囲を制限できる。

fixed

指定した固定幅を維持する。サイドバーやサムネイルなど、常に同じサイズを保ちたい列に使う。

adaptive

min で指定した最小幅に基づいて、スペースに収まるだけの列を自動生成する。画面サイズに応じて列数が変わるレスポンシブグリッドが作れる。

それぞれの具体的な使い方を見てみましょう。

// 固定幅と可変幅の組み合わせ
let columns1 = [
    GridItem(.fixed(80)),
    GridItem(.flexible()),
    GridItem(.flexible())
]

// 最小幅を指定して自動折り返し
let columns2 = [
    GridItem(.adaptive(minimum: 100, maximum: 200))
]

adaptive は 1 つの GridItem だけで「画面幅に応じた列数の自動調整」を実現します。写真ギャラリーやコレクション画面で、デバイスの画面サイズを気にせずレイアウトしたいときに最適です。

spacing の制御

LazyVGrid には行間の spacing を、GridItem には列間の spacing を指定できます。

let columns = [
    GridItem(.flexible(), spacing: 8),
    GridItem(.flexible(), spacing: 8),
    GridItem(.flexible())
]

ScrollView {
    LazyVGrid(columns: columns, spacing: 12) {
        ForEach(0..<30) { index in
            Color.purple.opacity(0.3)
                .frame(height: 80)
                .cornerRadius(8)
        }
    }
    .padding(.horizontal)
}

GridItem の spacing が列間(水平方向)、LazyVGrid の spacing が行間(垂直方向)を制御します。この 2 つを独立に調整することで、水平・垂直で異なる余白を持つグリッドを作れます。

LazyHGrid で横スクロールグリッド

LazyHGrid は横スクロールのグリッドを構築します。行(rows)を GridItem で定義する点が LazyVGrid と対照的です。

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

ScrollView(.horizontal) {
    LazyHGrid(rows: rows, spacing: 12) {
        ForEach(0..<30) { index in
            RoundedRectangle(cornerRadius: 8)
                .fill(Color.green.opacity(0.3))
                .frame(width: 120)
                .overlay(Text("\(index)"))
        }
    }
    .padding()
}

2 行固定で横にスクロールするグリッドになります。カテゴリの横スクロールや、カルーセル風の UI に適しています。

セクション付きグリッド

LazyVGrid の中で Section を使うと、グリッドにヘッダーやフッターを挟めます。

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

ScrollView {
    LazyVGrid(columns: columns, spacing: 16, pinnedViews: [.sectionHeaders]) {
        Section(header:
            Text("果物")
                .font(.headline)
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(.horizontal)
        ) {
            ForEach(fruits, id: \.self) { fruit in
                FruitCard(name: fruit)
            }
        }
    }
}

pinnedViews: [.sectionHeaders] を指定すると、スクロール時にヘッダーが上部に固定されます。カテゴリ別のグリッド表示で便利な機能です。Lazy グリッドは大量データの表示に強いので、スクロールパフォーマンスを意識する場面では積極的に活用しましょう。