【SwiftUI】Gridについて

# はじめに

iOS16から使える`Grid`について、ドキュメントを読んでみました
2次元のレイアウトを作成するのにとても便利そうでした

https://developer.apple.com/documentation/swiftui/grid

# 内容

Gridとその行は、VStackに包まれたHStackのコレクションのような動作をするが
行と列の作成を1つの操作として処理し、セルの間隔と配置を行と無関係の列に最初に適用するのではなく、セルに適用する

```swift
Grid {
    GridRow {
        Text("Hello")
        Image(systemName: "globe")
    }
    GridRow {
        Image(systemName: "hand.wave")
        Text("World")
    }
}
```

<img width="300" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/151fc20e-0cdb-d033-0d33-44888c1892c4.png">

背景に色をつけるとこんな感じに

<img width="300" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/42c6cc85-94ad-ecf8-b004-30b056135d09.png">

Gridの中に、GridRowではなくViewを指定した場合、GridはViewを使用してGridのすべての列にまたがる行を作成する

```swift
Grid {
    GridRow {
        Text("Hello")
        Image(systemName: "globe")
    }
    Divider()
    GridRow {
        Image(systemName: "hand.wave")
        Text("World")
    }
}
```

<img width="300" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/46b1bbc5-fd2a-2f60-0bd1-4464979ed798.png">

#### gridCellUnsizedAxes
[`gridCellUnsizedAxes(_:)`](https://developer.apple.com/documentation/swiftui/view/gridcellunsizedaxes(_:))を使うことで、Viewが行や列の他のセルが必要とする以上のスペースを取るのを防ぐことができる

```swift
Divider()
    .gridCellUnsizedAxes(.horizontal)
```

<img width="300" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/2c35dc62-e6c7-8ef8-b442-cfd879a2f835.png">

例えば、Textで使うと

<img width="300" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/12637f58-8e0f-65a9-abf1-64c4933c935d.png"> <img width="300" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/f2890e6b-4295-d779-a6e6-056fd6e5e93b.png">

#### gridCellColumns
Grid全体ではなく、特定の列数にセルをまたがるようにするには、GridRowに含まれるViewで[`gridCellColumns(_:)`](https://developer.apple.com/documentation/swiftui/view/gridcellcolumns(_:))を使用する

デフォルトでは、GridRowの中に入れた各Viewは、Gridのちょうど1つのカラムに対応する

```swift
Grid(alignment: .leadingFirstTextBaseline) {
    GridRow {
        Text("Regular font:")
            .gridColumnAlignment(.trailing)
        Text("Helvetica 12")
        Button("Select...") { }
    }
    GridRow {
        Text("Fixed-width font:")
        Text("Menlo Regular 11")
        Button("Select...") { }
    }
    GridRow {
        Color.clear
            .gridCellUnsizedAxes([.vertical, .horizontal])
        Toggle("Use fixed-width font for new documents", isOn: $isOn)
            .gridCellColumns(2) // Span two columns.
    }
}
```

<img width="300" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/1bf7e0b8-1e32-06f2-7d05-0ae6c2115c1a.png">

試しに`.gridCellColumns()`を
デフォルト、`.gridCellColumns(2)`、`.gridCellColumns(3)`で比べてみると
（ちょっと文字と色を変えましたが、さらに分かりにくい気も。。。）

<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/a3423e4f-4297-d4c6-a7e1-7ec984736954.png">
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/620fc879-c65c-0961-9e3d-2780f09da6ac.png">
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/f3fbaac7-36ba-6317-6c63-597f69e9d47f.png">

## カラム数
Gridの列数は、最大の列数を持つ行を処理するために増加し、異なる列数の行を作成した場合は列数が少ない行の後端に空のセルを追加する

```swift
Grid {
    GridRow {
        Text("Row 1")
        ForEach(0..<2) { _ in Color.red }
    }
    GridRow {
        Text("Row 2")
        ForEach(0..<5) { _ in Color.green }
    }
    GridRow {
        Text("Row 3")
        ForEach(0..<4) { _ in Color.blue }
    }
}
```

Gridは最も幅の広い行と同じ数の列を持ち、十分なViewを指定しない行には空のセルが追加されている

<img width="100" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/92d6d905-f75d-3b28-d7fb-2f8e21b5a85b.png"> <img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/515bec30-75a4-e5b2-b8e7-4f4c143d3c58.png">

カラムの最も幅の広いセルに合わせて、カラム内のすべてのセルの幅を設定する
最初の列の幅は、その列が含む最も広いテキストビューの幅に依存し
他の列は、親Viewが提供する残りの水平方向のスペースを等しく共有する

## セルの間隔と配置

Gridの[`init(alignment:horizontalSpacing:verticalSpacing:content:)`](https://developer.apple.com/documentation/swiftui/grid/init(alignment:horizontalspacing:verticalspacing:content:))を使って水平方向と垂直方向の両方でセル間の間隔を指定したり、全体のデフォルトの配置を指定することができる

```swift
Grid(alignment: .bottom, horizontalSpacing: 1, verticalSpacing: 1) {
    // ...
}
```

また、GridRow内のViewに[`gridColumnAlignment(_:)`](https://developer.apple.com/documentation/swiftui/view/gridcolumnalignment(_:))を追加することで水平方向の配置を変更したり

```swift
GridRow {
    Text("Regular font:")
        .gridColumnAlignment(.trailing) // Align the entire first column.
    Text("Helvetica 12")
    Button("Select...") { }
}
```

GridRowの[`init(alignment:content:) `](https://developer.apple.com/documentation/swiftui/gridrow/init(alignment:content:))を使うことで行のセルの垂直方向の配置を変更したりすることも可能

```swift
GridRow(alignment: .top) { // Use top vertical alignment.
    Text("Top")
    Color.red.frame(width: 1, height: 50)
    Color.blue.frame(width: 50, height: 1)
}
```

さらに、[`gridCellAnchor(_:)`](https://developer.apple.com/documentation/swiftui/view/gridcellanchor(_:))を使うことでセル単位の配置を変更できる

```swift
GridRow {
    Color.red.frame(width: 60, height: 60)
    Color.blue.frame(width: 10, height: 10)
        .gridCellAnchor(UnitPoint(x: 0.25, y: 0.75))
}
```

### パフォーマンス
Gridは、すべての子Viewを直ちにレンダリングするため
ScrollViewの内部で大きなGridを表示しアプリのパフォーマンスが低下する場合は
`LazyVGrid`や`LazyHGrid`を使った方が良い場合もある

# おまけ
同じUIを作るのに`Grid`を使った場合と、`VStack`&`HStack`の場合を比較してみました（これはドキュメントの内容とは全く関係ありません）

```swift: Gridで実装した場合
Grid(alignment: .center, horizontalSpacing: 5.0, verticalSpacing: 5.0) {
    GridRow {
        Text("Hello")
        Image(systemName: "globe")
        Text("Hoge")
    }
    .background(Color.blue)
    GridRow {
        Image(systemName: "hand.wave")
        Text("World")
        Text("Hoge")
    }
    .background(Color.yellow)
    GridRow {
        Text("Good")
        Image(systemName: "globe")
        Text("Hoge")
    }
    .background(Color.green)
    GridRow {
        Image(systemName: "hand.wave")
        Text("Morning")
        Text("Hoge")
    }
    .background(Color.gray)
}
.background(Color.red)
```

```swift: VStackとHStackで実装した場合
HStack(alignment: .center, spacing: 5.0) {
    VStack(spacing: 5.0) {
        Text("Hello")
            .background(Color.blue)
        Image(systemName: "hand.wave")
            .background(Color.yellow)
        Text("Good")
            .background(Color.green)
        Image(systemName: "hand.wave")
            .background(Color.gray)
    }
    VStack(spacing: 5.0) {
        Image(systemName: "globe")
            .background(Color.blue)
        Text("World")
            .background(Color.yellow)
        Image(systemName: "globe")
            .background(Color.green)
        Text("Morning")
            .background(Color.gray)
    }
    VStack(spacing: 5.0) {
        Text("Hoge")
            .background(Color.blue)
        Text("Hoge")
            .background(Color.yellow)
        Text("Hoge")
            .background(Color.green)
        Text("Hoge")
            .background(Color.gray)
    }
}
.background(Color.red)
```

UIは全く同じになりました
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/2c705b31-c2dc-10ee-568c-3dc2f1f90caa.png"> <img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/ec033a9e-54fd-cf3a-71d2-ee5523365c53.png">

さらに、等しく黄色のHogeとグレーのHogeを消すと。。。

<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/e3c8d008-6cb1-3f56-15ce-c87181b0c9b5.png"> <img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/12a60332-b9d1-81d4-63f6-aa778917ee7f.png">

この場合は、`Grid`を使うメリットがとてもありそうですね

# おわりに
UIのバランスを整えるのには、`Grid`を使うメリットもありそうだなと思いました。
もちろん`VStack`と`HStack`の方が適する場合も多いと思うので、都度考えて使っていこうと思います！