iOS15で画面サイズに応じて画像をトリミングする際にはGeometryReader × frameではなく、Rectangleを使った方が良さそう→シンプルな方法がありました。

## はじめに
```GeometryReader```・```LazyVStack```・```clipped```を使いトリミングした画像を画面の横幅いっぱいに広げる画面を実装したところ、レイアウトで詰まったので備忘録として解決方法を残します。

## 環境
MacBook Pro 13 inch M1
Xcode14.2
iOS15~

## 実現したいこと・要件
・iOS15以上（カスタムLayout・Gridは使えない）
・オリジナルの画像から正方形にトリミングしたい（画像の比率は変えたくない）
・画像は横に2枚づつ並べたい
・画像は画面幅いっぱいに広がるように並べたい
・スクロールできるようにしたい

イメージ
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160605/9c64bef2-c398-89e9-d78a-56e8785ce1ab.png" width="400">

## GeometryReaderを使ったところ問題が。。。

画像を切り取る際にはclippedが用意されておりますが、事前にframeを指定する必要があります。
GeometryReaderを使い、親Viewのサイズに応じてトリミングするframeの長さを指定する方針で実装しようと考えました。

```Swift
struct ContentView: View {
    let images: [Image] = .init(repeating: Image("IMG_6500"), count: 4)
    private let space: CGFloat = 2

    var body: some View {
        GeometryReader { geometry in
            // GeometryReaderの横幅を元に画像サイズを算出
            let imageWidth: CGFloat = geometry.size.width / 2 - space
            LazyVGrid(columns: .init(repeating: .init(.flexible(), spacing: space), count: 2), spacing: 2) {
                ForEach(images) { image in
                    image
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        // GeometryReaderの横幅を元に画像をトリミング
                        .frame(width: imageWidth, height: imageWidth)
                        .clipped()
                }
            }
        }
    }
}

extension Image: Identifiable {
    // TODO: プレビュー用にImageにIDを付与。実際のデータではImage型は直接利用せず、別の型にラップして使用予定
    public var id: UUID { UUID() }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

```
### プレビュー画面
ContentViewのみプレビューすると、良さそうですが、、、
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160605/fb656fd3-d431-05df-accc-12ba506bbd53.png" width="400">

以下のようにScrollView内でContentViewを実装しようとすると
```Swift
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ScrollView {
            Rectangle().frame(height:100)
            ContentView()
            Rectangle().frame(height:400)
        }
    }
}
```
うまく表示されません。。。。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160605/9511cefb-61ad-74b8-e905-154bfc3c9de1.png" width="400">

## GeometryReaderを使用する際は高さを指定する必要がある。。。
GeometryReaderは親Viewのサイズ（高さ・横幅）に合わせてViewのサイズを決定するため、
ContentViewに高さ```.frame(height:XXX)```を指定する必要があります。

```Swift
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        GeometryReader { geometry in
            ScrollView {
                Rectangle().frame(height: 100)
                ContentView()
                    .frame(height: geometry.size.width) // 高さ指定が必要（横幅と同じ高さを指定）
                Rectangle().frame(height: 400)
            }
        }
    }
}
```
ただし、そうなると、ContentViewを呼び出すViewも高さ`.frame(height:XXX)`を指定する必要があり、堂々巡りになっていきます。。。

## Rectangleを使った解決方法
画像をトリミングする方法を`frame`を指定してトリミングするのではなく、
矩形`Rectangle`を用意し、矩形に画像を重ね合わせ、トリミングする。

```Swift
struct ContentView: View {
    let images: [Image] = .init(repeating: Image("IMG_6500"), count: 4)
    private let space: CGFloat = 2

    var body: some View {
        LazyVGrid(columns: .init(repeating: .init(.flexible(), spacing: space), count: 2), spacing: 2) {
            ForEach(images) { image in
                Rectangle()
                    .aspectRatio(1, contentMode: .fit)
                    .overlay {
                        image
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                    }
                    .clipped()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ScrollView {
            Rectangle().frame(height: 100)
            ContentView()
            Rectangle().frame(height: 400)
        }
    }
}
```
このように実装することでGeometryReaderを使用せず、画像をトリミング、並べることができました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160605/59536c28-4cd3-4044-96c6-157a4fbbb17e.png)

## 追記（2023/05/04）
もっとシンプルな方法を@akio0911さんに教えていただきました！
```Swift
struct ContentView: View {
    let images: [Image] = .init(repeating: Image("IMG_6500"), count: 4)
    private let space: CGFloat = 2

    var body: some View {
        LazyVGrid(columns: .init(repeating: .init(.flexible(), spacing: space), count: 2), spacing: 2) {
            ForEach(images) { image in
                image
                    .resizable()
                    .scaledToFill()
                    .frame(minWidth: 0, maxWidth: .infinity)
                    .aspectRatio(1, contentMode: .fill)
                    .clipped()
            }
        }
    }
}
```

上記内容に関して、改善案・簡単に実装する方法・その他の方法等ありましたらコメントに記載いただければ幸いです。よろしくお願いいたします。

## 参考記事
Qiita記事 

[SwiftUI で 親 View に ScrollView、子 View に GeometryReader の構造にしたら、高さのレイアウトがおかしくなった](https://qiita.com/macneko/items/bb90084ac4ec4eb33393)

Twitter

https://twitter.com/1amageek/status/1377150060090118145?lang=ja