MENU

【SwiftUI】GridItemの使い方完全ガイド|グリッドレイアウトを徹底解説

SwiftUIでアプリを作る際、写真ギャラリーや商品一覧など、複数のアイテムをグリッド状に並べたい場面は多くあります。そんなときに活躍するのがGridItemです。

この記事では、SwiftUI初心者の方でも理解できるように、GridItemの基本から実践的な使い方まで、コード例を交えてわかりやすく解説します。

目次

GridItemとは?

GridItemは、SwiftUIでグリッドレイアウトを作成する際に、列や行のサイズや配置を定義する構造体です。

LazyVGrid(縦スクロールのグリッド)やLazyHGrid(横スクロールのグリッド)と組み合わせて使用し、「何列表示するか」「各列の幅はどうするか」といった設定を行います。

グリッドレイアウトとは?

グリッドレイアウトとは、コンテンツを格子状(碁盤の目のように)に配置するレイアウト方法です。写真アプリのサムネイル表示やECアプリの商品一覧などで使われています。

なぜGridItemが必要なのか?

従来、複数のアイテムを並べるにはHStackVStackを組み合わせる必要があり、コードが複雑になりがちでした。GridItemを使うことで、シンプルなコードで柔軟なグリッドレイアウトを実現できます。

GridItemのメリット:

  • レスポンシブなレイアウトが簡単に作れる
  • パフォーマンスが良い(Lazyなので必要な部分だけ描画)
  • コードがシンプルで読みやすい

GridItemの基本的な使い方

まずは最もシンプルな例から見ていきましょう。

最小限のサンプルコード

import SwiftUI

struct ContentView: View {
    // 3列のグリッドを定義
    let columns = [
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible())
    ]
    
    let items = Array(1...15)
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(items, id: \.self) { item in
                    Text("\(item)")
                        .frame(width: 80, height: 80)
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(8)
                }
            }
            .padding()
        }
    }
}

このコードは、数字を3列で表示するシンプルなグリッドを作成します。

コードの解説

1. GridItemの配列を定義

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

配列の要素数が列数になります。この場合は3つなので3列のグリッドになります。

2. LazyVGridで表示

LazyVGrid(columns: columns, spacing: 20) {
    // グリッドに表示する内容
}

  • columns: 先ほど定義したGridItemの配列
  • spacing: 行間のスペース(縦の間隔)

3. ForEachでアイテムを生成

ForEach(items, id: \.self) { item in
    // 各セルの内容
}

配列の各要素がグリッドのセルとして表示されます。

GridItemの3つのサイズタイプ

GridItemには、列や行のサイズを指定する3つの方法があります。これらを理解することが、GridItemを使いこなす鍵となります。

1. .flexible() – 柔軟なサイズ(最も一般的)

.flexible()は、利用可能なスペースを均等に分割します。

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

この場合、画面の横幅を2つに均等に分割します。

最小・最大サイズの指定も可能:

let columns = [
    GridItem(.flexible(minimum: 80, maximum: 200))
]

  • minimum: 列の最小幅(これより小さくならない)
  • maximum: 列の最大幅(これより大きくならない)

使用場面:

  • 画面サイズに応じて柔軟に調整したい場合
  • 均等な幅の列を作りたい場合

2. .fixed() – 固定サイズ

.fixed()は、指定したサイズで固定します。

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

各列が必ず100ポイントの幅になります。

使用場面:

  • アイコンなど固定サイズで表示したい場合
  • デザインで列幅が決まっている場合

注意点: 画面幅より大きい場合ははみ出します

3. .adaptive() – 適応サイズ(レスポンシブに最適)

.adaptive()は、指定したサイズに基づいて、できるだけ多くの列を自動的に配置します。

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

最小100ポイントで、画面幅に収まる数だけ列を作成します。

動作例:

  • iPhone(幅375pt)→ 3列
  • iPad(幅768pt)→ 7列

使用場面:

  • 写真ギャラリーなど、デバイスに応じて列数を変えたい場合
  • レスポンシブなレイアウトを作りたい場合

3つのサイズタイプの比較

実際に3つのタイプを比較してみましょう。

import SwiftUI

struct GridComparisonView: View {
    let items = Array(1...12)
    
    var body: some View {
        ScrollView {
            VStack(spacing: 40) {
                // 1. Flexible(柔軟)
                VStack(alignment: .leading) {
                    Text("1. Flexible - 柔軟なサイズ")
                        .font(.headline)
                        .padding(.horizontal)
                    
                    LazyVGrid(
                        columns: [
                            GridItem(.flexible()),
                            GridItem(.flexible()),
                            GridItem(.flexible())
                        ],
                        spacing: 10
                    ) {
                        ForEach(items, id: \.self) { item in
                            gridCell(item, color: .blue)
                        }
                    }
                    .padding(.horizontal)
                    
                    Text("→ 3列で画面幅を均等に分割")
                        .font(.caption)
                        .foregroundColor(.gray)
                        .padding(.horizontal)
                }
                
                // 2. Fixed(固定)
                VStack(alignment: .leading) {
                    Text("2. Fixed - 固定サイズ")
                        .font(.headline)
                        .padding(.horizontal)
                    
                    ScrollView(.horizontal, showsIndicators: false) {
                        LazyVGrid(
                            columns: [
                                GridItem(.fixed(100)),
                                GridItem(.fixed(100)),
                                GridItem(.fixed(100))
                            ],
                            spacing: 10
                        ) {
                            ForEach(items, id: \.self) { item in
                                gridCell(item, color: .green)
                            }
                        }
                        .padding(.horizontal)
                    }
                    
                    Text("→ 各列が必ず100pt幅")
                        .font(.caption)
                        .foregroundColor(.gray)
                        .padding(.horizontal)
                }
                
                // 3. Adaptive(適応)
                VStack(alignment: .leading) {
                    Text("3. Adaptive - 適応サイズ")
                        .font(.headline)
                        .padding(.horizontal)
                    
                    LazyVGrid(
                        columns: [
                            GridItem(.adaptive(minimum: 80))
                        ],
                        spacing: 10
                    ) {
                        ForEach(items, id: \.self) { item in
                            gridCell(item, color: .orange)
                        }
                    }
                    .padding(.horizontal)
                    
                    Text("→ 最小80ptで画面に収まる列数を自動調整")
                        .font(.caption)
                        .foregroundColor(.gray)
                        .padding(.horizontal)
                }
            }
            .padding(.vertical)
        }
    }
    
    func gridCell(_ number: Int, color: Color) -> some View {
        Text("\(number)")
            .frame(height: 60)
            .frame(maxWidth: .infinity)
            .background(color)
            .foregroundColor(.white)
            .cornerRadius(8)
    }
}

このコードを実行すると、3つのタイプの違いが視覚的に理解できます。

LazyVGridとLazyHGridの違い

GridItemは、縦スクロールのLazyVGridと横スクロールのLazyHGridの両方で使えます。

LazyVGrid – 縦スクロール(一般的)

struct VerticalGridView: View {
    let columns = [
        GridItem(.flexible()),
        GridItem(.flexible())
    ]
    
    var body: some View {
        ScrollView {  // 縦スクロール
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(1...20, id: \.self) { number in
                    Text("\(number)")
                        .frame(height: 80)
                        .frame(maxWidth: .infinity)
                        .background(Color.blue)
                        .cornerRadius(8)
                }
            }
            .padding()
        }
    }
}

ポイント: columnsパラメータを使用

LazyHGrid – 横スクロール

struct HorizontalGridView: View {
    let rows = [
        GridItem(.flexible()),
        GridItem(.flexible())
    ]
    
    var body: some View {
        ScrollView(.horizontal) {  // 横スクロール
            LazyHGrid(rows: rows, spacing: 20) {
                ForEach(1...20, id: \.self) { number in
                    Text("\(number)")
                        .frame(width: 80, height: 80)
                        .background(Color.green)
                        .cornerRadius(8)
                }
            }
            .padding()
        }
    }
}

ポイント: rowsパラメータを使用

GridItemのパラメータ詳細

GridItemには、サイズ以外にも設定できるパラメータがあります。

GridItem(
    .flexible(minimum: 80, maximum: 200),  // サイズタイプ
    spacing: 10,                            // この列の後のスペース
    alignment: .top                         // セル内での配置
)

spacing – 列間のスペース

各列の後ろにスペースを追加できます。

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

alignment – セル内の配置

セル内でのコンテンツの配置を指定できます。

let columns = [
    GridItem(.flexible(), alignment: .top),
    GridItem(.flexible(), alignment: .center),
    GridItem(.flexible(), alignment: .bottom)
]

実践例1: 写真ギャラリーアプリ

実際のアプリでよく使われる写真ギャラリーを作ってみましょう。

import SwiftUI

struct PhotoGalleryView: View {
    // 最小100pt、最大150ptの適応サイズ
    let columns = [
        GridItem(.adaptive(minimum: 100, maximum: 150))
    ]
    
    let photos = Array(1...30)
    
    var body: some View {
        NavigationView {
            ScrollView {
                LazyVGrid(columns: columns, spacing: 10) {
                    ForEach(photos, id: \.self) { number in
                        PhotoThumbnail(number: number)
                    }
                }
                .padding()
            }
            .navigationTitle("フォトギャラリー")
        }
    }
}

struct PhotoThumbnail: View {
    let number: Int
    
    var body: some View {
        ZStack(alignment: .bottomTrailing) {
            // 写真のサムネイル
            Image(systemName: "photo")
                .resizable()
                .aspectRatio(1, contentMode: .fit)
                .frame(maxWidth: .infinity)
                .background(Color.gray.opacity(0.3))
                .cornerRadius(8)
            
            // 写真番号のバッジ
            Text("\(number)")
                .font(.caption)
                .padding(4)
                .background(Color.black.opacity(0.7))
                .foregroundColor(.white)
                .cornerRadius(4)
                .padding(4)
        }
    }
}

このコードでは、画面サイズに応じて自動的に列数が調整される、レスポンシブな写真ギャラリーを実装しています。

実践例2: 商品一覧アプリ

ECアプリでよく見る商品一覧画面を作ってみましょう。

import SwiftUI

struct Product: Identifiable {
    let id = UUID()
    let name: String
    let price: Int
    let imageName: String
}

struct ProductGridView: View {
    let columns = [
        GridItem(.flexible()),
        GridItem(.flexible())
    ]
    
    let products = [
        Product(name: "ノートPC", price: 150000, imageName: "laptopcomputer"),
        Product(name: "マウス", price: 3000, imageName: "computermouse"),
        Product(name: "キーボード", price: 8000, imageName: "keyboard"),
        Product(name: "モニター", price: 40000, imageName: "display"),
        Product(name: "ヘッドフォン", price: 12000, imageName: "headphones"),
        Product(name: "スピーカー", price: 6000, imageName: "hifispeaker")
    ]
    
    var body: some View {
        NavigationView {
            ScrollView {
                LazyVGrid(columns: columns, spacing: 20) {
                    ForEach(products) { product in
                        ProductCard(product: product)
                    }
                }
                .padding()
            }
            .navigationTitle("商品一覧")
        }
    }
}

struct ProductCard: View {
    let product: Product
    
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            // 商品画像
            Image(systemName: product.imageName)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(height: 100)
                .frame(maxWidth: .infinity)
                .padding()
                .background(Color.gray.opacity(0.1))
                .cornerRadius(12)
            
            // 商品名
            Text(product.name)
                .font(.headline)
                .lineLimit(1)
            
            // 価格
            Text("¥\(product.price.formatted())")
                .font(.subheadline)
                .foregroundColor(.blue)
        }
        .padding()
        .background(Color.white)
        .cornerRadius(12)
        .shadow(color: .gray.opacity(0.2), radius: 5, x: 0, y: 2)
    }
}

実践例3: カレンダーアプリ

月間カレンダーのようなレイアウトも簡単に作れます。

import SwiftUI

struct CalendarView: View {
    // 7列固定(月〜日)
    let columns = Array(repeating: GridItem(.flexible()), count: 7)
    
    let weekdays = ["月", "火", "水", "木", "金", "土", "日"]
    let days = Array(1...31)
    
    var body: some View {
        VStack {
            // 曜日ヘッダー
            LazyVGrid(columns: columns, spacing: 0) {
                ForEach(weekdays, id: \.self) { day in
                    Text(day)
                        .font(.caption)
                        .fontWeight(.bold)
                        .frame(height: 30)
                        .frame(maxWidth: .infinity)
                        .background(Color.gray.opacity(0.2))
                }
            }
            
            // 日付グリッド
            ScrollView {
                LazyVGrid(columns: columns, spacing: 0) {
                    ForEach(days, id: \.self) { day in
                        Text("\(day)")
                            .frame(height: 50)
                            .frame(maxWidth: .infinity)
                            .background(Color.white)
                            .border(Color.gray.opacity(0.3), width: 0.5)
                    }
                }
            }
        }
        .navigationTitle("2024年12月")
    }
}

よくあるエラーと対処法

エラー1: グリッドが表示されない

原因: ScrollViewやLazyVGridのコンテナが適切に配置されていない

解決方法:

// ❌ 間違い
LazyVGrid(columns: columns) {
    ForEach(items, id: \.self) { item in
        Text("\(item)")
    }
}

// ✅ 正しい - ScrollViewで囲む
ScrollView {
    LazyVGrid(columns: columns) {
        ForEach(items, id: \.self) { item in
            Text("\(item)")
        }
    }
}

エラー2: 列数が変わらない

原因: .adaptive()を使っていない、またはminimumの値が大きすぎる

解決方法:

// レスポンシブにしたい場合は.adaptive()を使う
let columns = [
    GridItem(.adaptive(minimum: 80))  // 最小サイズを調整
]

エラー3: セルのサイズが揃わない

原因: セルにframe(maxWidth: .infinity)を指定していない

解決方法:

Text("\(item)")
    .frame(maxWidth: .infinity)  // これを追加
    .frame(height: 80)
    .background(Color.blue)

エラー4: パフォーマンスが悪い

原因: LazyではなくVGridを使っている、または複雑な計算を各セルで行っている

解決方法:

// ❌ Lazyではない(全て一度に描画される)
// VGrid(columns: columns) { ... }

// ✅ Lazy(表示される部分だけ描画)
LazyVGrid(columns: columns) { ... }

GridItemを使う際のベストプラクティス

1. 用途に応じてサイズタイプを選ぶ

// 均等な列 → .flexible()
let evenColumns = [GridItem(.flexible()), GridItem(.flexible())]

// 固定サイズ → .fixed()
let iconColumns = Array(repeating: GridItem(.fixed(60)), count: 5)

// レスポンシブ → .adaptive()
let photoColumns = [GridItem(.adaptive(minimum: 100))]

2. spacingを適切に設定する

LazyVGrid(
    columns: columns,
    spacing: 16  // 行間のスペース
) {
    ForEach(items) { item in
        // セルの内容
    }
}
.padding()  // グリッド全体の余白

3. 必ずScrollViewで囲む

// ✅ 正しい構造
ScrollView {
    LazyVGrid(columns: columns) {
        // 内容
    }
}

4. セルのサイズを明示的に指定する

// セルの高さは明示的に指定
Text("\(item)")
    .frame(height: 80)
    .frame(maxWidth: .infinity)

5. パフォーマンスのためにLazyを使う

大量のデータを扱う場合は必ずLazyVGrid/LazyHGridを使いましょう。

応用テクニック

異なる幅の列を作る

let columns = [
    GridItem(.flexible(minimum: 100, maximum: 200)),  // 広い列
    GridItem(.flexible(minimum: 50, maximum: 100))    // 狭い列
]

固定列と柔軟な列を組み合わせる

let columns = [
    GridItem(.fixed(80)),      // アイコン列(固定)
    GridItem(.flexible()),     // コンテンツ列(柔軟)
    GridItem(.fixed(60))       // アクション列(固定)
]

動的に列数を変更する

struct DynamicGridView: View {
    @State private var columnCount = 2
    
    var columns: [GridItem] {
        Array(repeating: GridItem(.flexible()), count: columnCount)
    }
    
    var body: some View {
        VStack {
            Picker("列数", selection: $columnCount) {
                Text("2列").tag(2)
                Text("3列").tag(3)
                Text("4列").tag(4)
            }
            .pickerStyle(.segmented)
            .padding()
            
            ScrollView {
                LazyVGrid(columns: columns, spacing: 10) {
                    ForEach(1...20, id: \.self) { number in
                        Text("\(number)")
                            .frame(height: 60)
                            .frame(maxWidth: .infinity)
                            .background(Color.blue)
                            .foregroundColor(.white)
                            .cornerRadius(8)
                    }
                }
                .padding()
            }
        }
    }
}

まとめ

この記事では、SwiftUIのGridItemについて詳しく解説しました。

重要なポイント:

  • GridItemはグリッドレイアウトの列や行を定義する
  • 3つのサイズタイプ:.flexible()(柔軟)、.fixed()(固定)、.adaptive()(適応)
  • LazyVGridは縦スクロール、LazyHGridは横スクロール
  • レスポンシブなレイアウトには.adaptive()が最適
  • パフォーマンスのためにLazyを使う

サイズタイプの選び方:

  • 均等な幅の列: .flexible()
  • 決まったサイズ: .fixed()
  • デバイスに応じて調整: .adaptive()

GridItemを使いこなすことで、写真ギャラリー、商品一覧、カレンダーなど、様々なグリッドレイアウトを簡単に実装できます。まずは基本の.flexible()から始めて、徐々に他のタイプも試してみてください。

プログラミングの独学におすすめ
プログラミング言語の人気オンラインコース
独学でプログラミングを学習している方で、エラーなどが発生して効率よく勉強ができないと悩む方は多いはず。Udemyは、プロの講師が動画で実際のプログラムを動かしながら教えてくれるオンライン講座です。講座の価格は、セール期間中には専門書籍を1冊買うよりも安く済むことが多いです。新しく学びたいプログラミング言語がある方は、ぜひUdemyでオンライン講座を探してみてください。
目次