SwiftUI の Grid(iOS 16+)で柔軟なグリッドを組む

iOS 16 で導入された Grid は、LazyVGrid よりも細かいセルレベルの制御が可能なグリッドコンテナです。行と列を明示的に定義し、セルの結合(マージ)やカスタム配置を宣言的に記述できます。

基本構造

Grid は GridRow で各行を定義し、その中に子ビューを列として並べます。

Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 8) {
    GridRow {
        Text("名前")
            .fontWeight(.bold)
        Text("年齢")
            .fontWeight(.bold)
        Text("国")
            .fontWeight(.bold)
    }
    Divider()
    GridRow {
        Text("Alice")
        Text("28")
        Text("Japan")
    }
    GridRow {
        Text("Bob")
        Text("34")
        Text("USA")
    }
}
.padding()

LazyVGrid と違い、Grid は GridItem の配列を定義する必要がありません。各 GridRow 内のビューの数がそのまま列数になり、すべての行の同じ列が自動的に幅を揃えます。

Grid と LazyVGrid の違い

両者は見た目が似ていますが、設計思想が異なります。

Grid(iOS 16+)

すべてのセルを同時にレイアウトする。セルの結合や列ごとの alignment が可能。少数の構造化データ向き

LazyVGrid

画面外のビューは遅延生成する。大量データの一覧表示向き。セルの結合はできない

Grid は全セルを一度にレイアウトするため、Lazy ではありません。数百行のデータ表示には向きませんが、フォームや表形式の UI には Grid の方が表現力が高くなります。

gridCellColumns でセルを結合する

gridCellColumns モディファイアを使うと、1 つのセルが複数の列にまたがるようになります。

Grid {
    GridRow {
        Text("項目")
            .fontWeight(.bold)
        Text("値")
            .fontWeight(.bold)
    }
    Divider()
    GridRow {
        Text("CPU")
        Text("Apple M2")
    }
    GridRow {
        Text("メモリ")
        Text("16GB")
    }
    GridRow {
        Text("補足: このスペックは 2024 年モデルのものです")
            .font(.caption)
            .foregroundColor(.secondary)
            .gridCellColumns(2)
    }
}
.padding()

最後の行で .gridCellColumns(2) を指定しているため、テキストが 2 列分の幅にまたがって表示されます。注釈やフッターなど、行全体を使いたい場面で便利です。

gridCellAnchor でセル内の配置を制御する

Grid 全体の alignment とは別に、個々のセルの配置を gridCellAnchor で上書きできます。

Grid(alignment: .leading) {
    GridRow {
        Text("左寄せ")
        Text("中央寄せ")
            .gridCellAnchor(.center)
        Text("右寄せ")
            .gridCellAnchor(.trailing)
    }
    GridRow {
        Text("Apple")
        Text("100")
            .gridCellAnchor(.center)
        Text("¥200")
            .gridCellAnchor(.trailing)
    }
}
.padding()

商品名は左寄せ、数量は中央、価格は右寄せといったテーブル表現が宣言的に書けます。

GridRow 外のビューは行全体に広がる

Grid の直下に GridRow を介さずビューを置くと、そのビューは全列にまたがって配置されます。

Grid {
    GridRow {
        Text("A")
        Text("B")
        Text("C")
    }
    
    Divider()
    
    GridRow {
        Text("1")
        Text("2")
        Text("3")
    }
}

Divider は GridRow で囲まれていないため、3 列分の幅で水平線が引かれます。セクション区切りを入れたいときに、gridCellColumns を使わなくても自然に表現できる仕組みです。

実践例:設定フォーム

Grid を使えば、ラベルと入力フィールドが整列したフォームを簡潔に構築できます。

Grid(alignment: .trailing, horizontalSpacing: 12, verticalSpacing: 16) {
    GridRow {
        Text("名前")
        TextField("入力してください", text: $name)
            .textFieldStyle(.roundedBorder)
            .gridCellAnchor(.leading)
    }
    GridRow {
        Text("メール")
        TextField("example@mail.com", text: $email)
            .textFieldStyle(.roundedBorder)
            .gridCellAnchor(.leading)
    }
    GridRow {
        Text("メモ")
        TextEditor(text: $memo)
            .frame(height: 80)
            .overlay(
                RoundedRectangle(cornerRadius: 8)
                    .stroke(Color.gray.opacity(0.3))
            )
            .gridCellAnchor(.leading)
    }
}
.padding()

ラベル列は trailing(右寄せ)、入力列は leading(左寄せ)で配置することで、ラベルとフィールドの境界がきれいに揃います。LazyVGrid では実現しにくいこの種の整列が、Grid なら直感的に書けるのが大きな魅力です。