GeometryReader自体の高さを子ビューのフレームサイズと同じにする
記事タイトルをどうすべきか結構悩んだんですがw、要はやりたいのはこういうことです。
Rectangleの高さを、与えられたフレーム幅を基に動的に変化させたいとします。
ここではフレーム幅の半分のサイズの高さを指定することとします。
struct ContentView: View { var body: some View { VStack { Text("Hello") GeometryReader { geometry in Rectangle() .fill(Color.blue) .frame(width: geometry.size.width, height: geometry.size.width / 2) } Text("Hello") } } }
こんな画面になります。
Rectangleのフレームサイズはframe(width:height:)
で指定した通りですが、GeometryReader自体は「Hello」から「Hello」までのフレームサイズを持っています。
GeometryReader自体のフレームサイズを指定していないので、親であるVStack内の利用可能な領域いっぱいまでフレームサイズが広がるようです。
次に、VStackをScrollView内に入れてみます。
struct ContentView: View { var body: some View { ScrollView(.vertical) { // 追加 VStack { Text("Hello") GeometryReader { geometry in Rectangle() .fill(Color.blue) .frame(width: geometry.size.width, height: geometry.size.width / 2) } Text("Hello") } } } }
すると、こんな画面になります。
ScrollViewの中に入ったので、GeometryReaderの高さが不定?になり、Rectangleが下の「Hello」の領域まではみ出てしまっています。
子ビューであるRectangleの高さと同じ高さになることを期待してましたが、そうではないようです。
どうすれば期待通りにできるでしょうか?
私は以下のようなやり方で解決しました。
- 子ビュー(Rectangle)の高さを取得する
- 子ビューの高さをGeometryReaderの
frame(height:)
に指定する
実装の全体はこんな感じです。
struct ContentView: View { @State private var rectangleHeight: CGFloat = .zero var body: some View { ScrollView(.vertical) { VStack { Text("Hello") GeometryReader { geometry in Rectangle() .fill(Color.blue) .frame(width: geometry.size.width, height: geometry.size.width / 2) .readHeight($rectangleHeight) } .frame(height: rectangleHeight) Text("Hello") } } } } extension View { func readHeight(_ height: Binding<CGFloat>) -> some View { background(GeometryReader { geometry -> Color in DispatchQueue.main.async { height.wrappedValue = geometry.size.height } return Color.clear }) } }
ビューの高さを取得することができるreadHeight(_:)
というModifierを定義しています。
func readHeight(_ height: Binding<CGFloat>) -> some View
backgroundにGeometryReaderをあてることで、そのビューのフレームサイズを取得することができるという機能を利用しています。
この実装については以下のリンクが参考になると思うので見てみてください。
readHeight(_:)
で取得したRectangleの高さは@Stateプロパティに格納され、それをGeometryReaderの高さとして指定しています。
@State private var rectangleHeight: CGFloat = .zero ... ScrollView(.vertical) { VStack { Text("Hello") GeometryReader { geometry in Rectangle() .fill(Color.blue) .frame(width: geometry.size.width, height: geometry.size.width / 2) .readHeight($rectangleHeight) // ここでRectangleの高さを取得 } .frame(height: rectangleHeight) // Rectangleの高さを指定 Text("Hello") } }
こうすることで、GeometryReaderがRectangle分のフレームサイズを持つことになり、意図した画面にすることができます。