SwiftUI ScrollView VStackGeometryReaderの高さは無視されます

Oct 10 2020

VStackの外部でScrollViewを使用して、VStackが画面サイズを超えて拡張した場合にコンテンツをスクロールできるようにしたい。GeometryReaderVStack内で使用したいのですが、問題が発生します。これは、GeometryReaderフレームを設定することによってのみ解決できます。これは、リーダーを使用してビューサイズを定義しているため、あまり役に立ちません。

これがScrollViewのないコードで、うまく機能します。

struct MyExampleView: View {
  var body: some View {
    VStack {
        Text("Top Label")
            .background(Color.red)
        
        GeometryReader { reader in
            Text("Custom Sized Label")
                .frame(width: reader.size.width, height: reader.size.width * 0.5)
                .background(Color.green)
        }
        
        Text("Bottom Label")
            .background(Color.blue)
    }
    .background(Color.yellow)
  }
}

これにより、次の画像が表示されます。

カスタムサイズのラベルは全幅である必要がありますが、高さの場合は幅の半分です。同じコードをScrollViewでラップすると、次のようになります。

すべてが小さくなっただけでなく、カスタムサイズのラベルの高さはどういうわけか無視されます。GeometryReaderの高さを設定すると、その動作を調整できますが、GeometryReaderをそのコンテンツと同じ大きさに拡大したいと思います。どうすればこれを達成できますか?

ありがとう

回答

1 Asperi Oct 10 2020 at 20:02

これはGeometryReader魔法のツールではなく、現在のコンテキストの親で使用可能なスペースを読み取るだけですScrollViewが、内部コンテンツから必要なスペースを決定するため、独自の使用可能なスペースがなく、ゼロであることが理解されGeometryReaderます。サイクルがあります-子は親にサイズを要求しますが、親は子にサイズを期待します... SwiftUIレンダラーは、クラッシュしないように、これを何らかの方法で解決します(最小の既知のサイズを見つけます)。

これがあなたのシナリオのための可能な解決策です-ここでの適切な手段はビュー設定です。Xcode 12 / iOS14で準備およびテスト済み。

struct DemoLayout_Previews: PreviewProvider {
    static var previews: some View {
        Group {
          MyExampleView()
          ScrollView { MyExampleView() }
        }
    }
}

struct MyExampleView: View {
    @State private var height = CGFloat.zero
    var body: some View {
        VStack {
            Text("Top Label")
                .background(Color.red)
            
            Text("Custom Sized Label")
                .frame(maxWidth: .infinity)
                .background(GeometryReader {
                    // store half of current width (which is screen-wide)
                    // in preference
                    Color.clear
                        .preference(key: ViewHeightKey.self, 
                            value: $0.frame(in: .local).size.width / 2.0) }) .onPreferenceChange(ViewHeightKey.self) { // read value from preference in state self.height = $0
                }
                .frame(height: height) // apply from stored state
                .background(Color.green)
            
            Text("Bottom Label")
                .background(Color.blue)
        }
        .background(Color.yellow)
    }
}

struct ViewHeightKey: PreferenceKey {
    typealias Value = CGFloat
    static var defaultValue = CGFloat.zero
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value += nextValue()
    }
}

注:...ビューのコンテキストがわからない場合は、GeometryReaderを使用しないでください。