LoginSignup
5
3

More than 1 year has passed since last update.

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

Posted at

この記事の内容

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

環境

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

実験結果

まずは最もシンプルなもの。画面にGeometryReaderを一つだけ配置

ContentViewの中に一つだけ、高さゼロのTextGeometryReaderに入れたものを置いたです。GeometryReaderで取得した位置とサイズの情報をprintでコンソール出力しています。

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つ配置

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

と、高さがGeometryReader1つの場合の半分程度になっており、2つのGeometryReaderに描画用の領域が分けて与えられていることが分かります。
もう一つ興味深いのはprint文の実行順序です。下に置いたi:1の方が先に動いていることが分かります。

参考)画面キャプチャ:
image.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

ScrollViewにVStackでGeometryReaderを2つ置く(高さ指定あり)

高さ指定しないと潰れてしまいましたが、ScrollViewの中でも高さを指定すればその通り動きます

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

ScrollViewで高さを指定せずにGeometryReaderを使えないか?->難しい

テキストを10個ほど隙間なく並べます。高さが途中で変わるように、5個目以降は2行にしています。

まずはGeometryReaderを使わない場合のコードとキャプチャです。

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

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

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
思いっきりくずれます。ScrollViewの中で高さ指定なしでGeometryReaderを使うと(この場合は)高さ10で描画領域が確保され、その中に詰め込むのでこうなるようです。

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

まとめ

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3