【SwiftUI】GeometryReaderを複数使うときの挙動がよくわからんので実験と整理

# この記事の内容
「複数のViewでそれぞれ`GeometryReader`を使い、取得した位置・サイズによって処理を分岐させる」ということがしたかったのですが、`GeometryReader`の挙動が想定通りにならず不明点が多いので、色々と実験しました。その結果と分かったことをまとめます。

# 環境
- macOS Monterey v.12.5
- Xcode v.13.4.1
    - 主にiPhoneSE(3rd)(iOS15.5)のシミュレーターを利用

# 実験結果
## まずは最もシンプルなもの。画面にGeometryReaderを一つだけ配置
`ContentView`の中に一つだけ、高さゼロの`Text`を`GeometryReader`に入れたものを置いたです。`GeometryReader`で取得した位置とサイズの情報を`print`でコンソール出力しています。

```swift
GeometryReader {geo in
    Text("").frame(height:0)  // 高さゼロのviewを一つ置いた場合
    let _ = print("y:\(geo.frame(in:.global).origin.y), h:\(geo.frame(in:.global).height)")
}
```
`print`の結果は
```
y:20.0, h:647.0`
```
でした。`GeometryReader`が返すのは、中の`Text`の大きさではなく、**描画のために利用可能な領域**の情報なので、高さが647となっています。これは、`GeometryReader`を2つ置いてみるとより分かりやすいです。

## VStackでGeometryReaderを2つ配置
```swift
VStack{
    GeometryReader {geo in
        Text("1")
        let _ = print("i:0, y:\(geo.frame(in:.global).origin.y), h:\(geo.frame(in:.global).height)")
    }.background(Color.yellow.opacity(0.2))

    GeometryReader {geo in
        Text("2")
        let _ = print("i:1, y:\(geo.frame(in:.global).origin.y), h:\(geo.frame(in:.global).height)")
    }.background(Color.blue.opacity(0.2))
}
```
この結果は
```
i:1, y:347.5, h:319.5
i:0, y:20.0, h:319.5
```
と、高さが`GeometryReader`1つの場合の半分程度になっており、**2つの`GeometryReader`に描画用の領域が分けて与えられている**ことが分かります。
もう一つ興味深いのは`print文`の実行順序です。**下に置いた`i:1`の方が先に動いている**ことが分かります。

参考）画面キャプチャ：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734189/f87c744a-d07b-fe54-b163-9b0e529e708d.png)

# ScrollViewにVStackでGeometryReaderを2つ置く（高さ指定なし）
同じことを`ScrollView`の中でやると、結果が変わります

（コードは割愛）
print内容です
```
i:1, y:38.0, h:10.0
i:0, y:20.0, h:10.0
```
高さが10になっています。**ScrollViewの中では高さを指定しないと上手くいかない**ようです。

キャプチャはこちら：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734189/79de8d93-59cf-5be3-2834-13448be056fd.png)

# ScrollViewにVStackでGeometryReaderを2つ置く（高さ指定あり）
高さ指定しないと潰れてしまいましたが、**ScrollViewの中でも高さを指定すればその通り動きます**。
```swift
ScrollView{
    VStack(){
        GeometryReader {geo in
            Text("1")
            let _ = print("i:0, y:\(geo.frame(in:.global).origin.y), h:\(geo.frame(in:.global).height)")
        }
        .frame(height:50)
        .background(Color.yellow.opacity(0.2))

        GeometryReader {geo in
            Text("2")
            let _ = print("i:1, y:\(geo.frame(in:.global).origin.y), h:\(geo.frame(in:.global).height)")
        }
        .frame(height:50)
        .background(Color.blue.opacity(0.2))
    }
}
```
print：
```
i:1, y:78.0, h:50.0
i:0, y:20.0, h:50.0
```

参考）キャプチャ：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734189/fc7a7414-5ee7-a1c9-6a0f-0db95dd74849.png)


# ScrollViewで高さを指定せずにGeometryReaderを使えないか？->難しい
テキストを10個ほど隙間なく並べます。高さが途中で変わるように、5個目以降は2行にしています。

まずはGeometryReaderを使わない場合のコードとキャプチャです。
```swift
ScrollView{
    VStack(spacing:1){
        ForEach(0..<10){ num in
            let text:String = num < 5
                ? "num=\(num)"
                : "num=\(num)の1行目\nnum=\(num)の2行目"
            Text(text)
                .lineLimit(nil)
                .background(Color.blue.opacity(0.2))
        }
    }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734189/41362481-f0cc-c17a-83b1-85d770711aba.png)

意図した通りに描画できています。
では次に、各Textのところで`GeometryReader`を使います。

```swift
ScrollView{
    VStack(spacing:1){
        ForEach(0..<10){ num in
            let text:String = num < 5
                ? "num=\(num)"
                : "num=\(num)の1行目\nnum=\(num)の2行目"
            GeometryReader{geo in
                Text(text)
                    .lineLimit(nil)
                    .background(Color.blue.opacity(0.2))
                let _ = print("num:\(num),y:\(geo.frame(in:.global).origin.y),h:\(geo.frame(in:.global).height)")
            }
        }
    }
}
```
キャプチャ：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734189/a4a90d50-a403-1a17-c20f-f37df682facc.png)
思いっきりくずれます。`ScrollView`の中で高さ指定なしで`GeometryReader`を使うと(この場合は)高さ10で描画領域が確保され、その中に詰め込むのでこうなるようです。

この問題は解決できませんでした。どうしてもスクロール＋GeometryReaderを使いたければ、スクロール機能は自前で実装しないといけないんでしょうか。今のところ自分にはそのニーズはないので保留。。。

# まとめ
- `GeometryReader`が返すのは、子ビューでも親ビューでもなく描画可能領域の情報
- 複数の`GeometryReader`を`VStack`で使った場合、コードの順とは逆に、下の`GeometryReader`内の処理が先に実行された様子（詳細不明、print文の順番がそうだっただけ）
- `ScrollView`の中で`GeometryReader`を使う場合は高さ指定が必要