この記事の内容
「複数のViewでそれぞれGeometryReader
を使い、取得した位置・サイズによって処理を分岐させる」ということがしたかったのですが、GeometryReader
の挙動が想定通りにならず不明点が多いので、色々と実験しました。その結果と分かったことをまとめます。
環境
- macOS Monterey v.12.5
- Xcode v.13.4.1
- 主にiPhoneSE(3rd)(iOS15.5)のシミュレーターを利用
実験結果
まずは最もシンプルなもの。画面にGeometryReaderを一つだけ配置
ContentView
の中に一つだけ、高さゼロのText
をGeometryReader
に入れたものを置いたです。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
と、高さがGeometryReader
1つの場合の半分程度になっており、2つのGeometryReader
に描画用の領域が分けて与えられていることが分かります。
もう一つ興味深いのはprint文
の実行順序です。下に置いたi:1
の方が先に動いていることが分かります。
ScrollViewにVStackでGeometryReaderを2つ置く(高さ指定なし)
同じことをScrollView
の中でやると、結果が変わります
(コードは割愛)
print内容です
i:1, y:38.0, h:10.0
i:0, y:20.0, h:10.0
高さが10になっています。ScrollViewの中では高さを指定しないと上手くいかないようです。
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
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))
}
}
}
意図した通りに描画できています。
では次に、各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)")
}
}
}
}
キャプチャ:
思いっきりくずれます。ScrollView
の中で高さ指定なしでGeometryReader
を使うと(この場合は)高さ10で描画領域が確保され、その中に詰め込むのでこうなるようです。
この問題は解決できませんでした。どうしてもスクロール+GeometryReaderを使いたければ、スクロール機能は自前で実装しないといけないんでしょうか。今のところ自分にはそのニーズはないので保留。。。
まとめ
-
GeometryReader
が返すのは、子ビューでも親ビューでもなく描画可能領域の情報 - 複数の
GeometryReader
をVStack
で使った場合、コードの順とは逆に、下のGeometryReader
内の処理が先に実行された様子(詳細不明、print文の順番がそうだっただけ) -
ScrollView
の中でGeometryReader
を使う場合は高さ指定が必要