SwiftUIでアプリを作る際、写真ギャラリーや商品一覧など、複数のアイテムをグリッド状に並べたい場面は多くあります。そんなときに活躍するのがGridItem
です。
この記事では、SwiftUI初心者の方でも理解できるように、GridItem
の基本から実践的な使い方まで、コード例を交えてわかりやすく解説します。
GridItemとは?
GridItem
は、SwiftUIでグリッドレイアウトを作成する際に、列や行のサイズや配置を定義する構造体です。
LazyVGrid
(縦スクロールのグリッド)やLazyHGrid
(横スクロールのグリッド)と組み合わせて使用し、「何列表示するか」「各列の幅はどうするか」といった設定を行います。
グリッドレイアウトとは?
グリッドレイアウトとは、コンテンツを格子状(碁盤の目のように)に配置するレイアウト方法です。写真アプリのサムネイル表示やECアプリの商品一覧などで使われています。
なぜGridItemが必要なのか?
従来、複数のアイテムを並べるにはHStack
やVStack
を組み合わせる必要があり、コードが複雑になりがちでした。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()
から始めて、徐々に他のタイプも試してみてください。