SwiftUI ScrollView VStack GeometryReader高度被忽略

5
我希望在VStack之外使用ScrollView,这样如果VStack超出屏幕大小,我的内容就可以滚动。现在我想在VStack内使用GeometryReader,但它会导致问题,我只能通过设置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个回答

5
应该明白,GeometryReader并不是一个神奇的工具,它只是读取当前上下文父视图中可用的空间,但是ScrollView没有自己的可用空间,它是零,因为它从内部内容确定所需的空间...因此在这里使用GeometryReader会出现循环-子视图要求其父视图提供大小,而父视图期望获得来自子视图的大小... SwiftUI渲染器以某种方式解决了这个问题(找到已知最小尺寸),仅仅是为了避免崩溃。

以下是您情况的可能解决方案 - 此处适当的工具是视图偏好设置。已经在Xcode 12 / iOS 14下进行了准备和测试。

演示

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。


1
谢谢!偏好值到底在哪里设置?如果我将初始值设为0,我的视图高度就是0。如果我将其设置为10,则为10。 - Janosch Hübner
在同一渲染周期中,查看首选项传递子级到父级的值,添加更多注释。 - Asperi
1
谢谢!很奇怪,它对我来说不起作用。似乎onPreferenceChanged没有被调用。我会处理这个问题的。 - Janosch Hübner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接