ScrollView无法与GeometryReader一起正常使用

时间:2020-05-04 23:32:28

标签: ios swift swiftui scrollview geometryreader

scrollview和GeometryReader出现一些问题。我想在图像下有一个项目列表。并且每个项目都应具有以下宽度和高度:
((width of the entire screen - leading padding - trailing padding) / 2)

对于我的用例,我尝试了两种方法。这是我的第一个代码结构:

方法1

ScrollView
   - VStack
      - Image
      - GeometryReader
         - ForEach
            - Text

我正在使用几何读取器来获取VStack的宽度,因为它具有填充,并且我不想具有滚动视图的完整宽度。

但是对于GeometryReader,UI中仅显示ForEach循环中的最后一项。而且GeometryReader的高度很小。查看屏幕截图。

代码:

import SwiftUI

struct Item: Identifiable {
    var id = UUID().uuidString
    var value: String
}

struct ContentView: View {

    func items() -> [Item] {
        var items = [Item]()
        for i in 0..<100 {
            items.append(Item(value: "Item #\(i)"))
        }
        return items
    }

    var body: some View {
        ScrollView {
            VStack {
                Image(systemName: "circle.fill")
                    .resizable()
                    .scaledToFill()
                    .frame(width: 150, height: 150)
                    .padding()
                    .foregroundColor(.green)

                GeometryReader { geometry in
                    ForEach(self.items()) { item in
                        Text(item.value)
                            .frame(width: geometry.size.width / CGFloat(2), height: geometry.size.width / CGFloat(2))
                            .background(Color.red)
                    }
                }
                .background(Color.blue)
            }
            .frame(maxWidth: .infinity)
            .padding()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

红色是ForEach循环中的项目。蓝色的GeometryReader和绿色的只是图像。

Screenshot



方法2

ScrollView
   -GeometryReader
      - VStack
         - Image
         - ForEach
            -Text

然后正确显示我的ForEach循环中的项目,但无法再滚动。

代码

import SwiftUI

struct Item: Identifiable {
    var id = UUID().uuidString
    var value: String
}

struct ContentView: View {

    func items() -> [Item] {
        var items = [Item]()
        for i in 0..<100 {
            items.append(Item(value: "Item #\(i)"))
        }
        return items
    }

    var body: some View {
        ScrollView {
            GeometryReader { geometry in
                VStack {
                    Image(systemName: "circle.fill")
                        .resizable()
                        .scaledToFill()
                        .frame(width: 150, height: 150)
                        .padding()
                        .foregroundColor(.green)

                    ForEach(self.items()) { item in
                        Text(item.value)
                            .frame(width: geometry.size.width / CGFloat(2), height: geometry.size.width / CGFloat(2))
                            .background(Color.red)
                    }
                }
                .frame(maxWidth: .infinity)
            }
            .padding()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

screenshotNo2

如何存档以正确显示UI。我在这里想念什么吗?

非常感谢您的帮助。
谢谢。



编辑

我找到了一种解决方法,可以使用可工作的scrollView正确呈现UI,但对我来说似乎很棘手。
我正在使用PreferenceKey来解决此问题。

我正在使用高度为0的scrollview中的geometryReader。仅用于获取VStack的宽度。 在preferenceKeyChange上,我正在更新状态变量,并将其用于我的商品以设置其宽度和高度。

import SwiftUI

struct Item: Identifiable {
    var id = UUID().uuidString
    var value: String
}

struct ContentView: View {
    @State var width: CGFloat = 0

    func items() -> [Item] {
        var items = [Item]()
        for i in 0..<100 {
            items.append(Item(value: "Item #\(i)"))
        }
        return items
    }

    struct WidthPreferenceKey: PreferenceKey {
        typealias Value = [CGFloat]

        static var defaultValue: [CGFloat] = [0]

        static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) {
            value.append(contentsOf: nextValue())
        }
    }

    var body: some View {
        ScrollView {
            VStack(spacing: 0) {
                Image(systemName: "circle.fill")
                    .resizable()
                    .scaledToFill()
                    .frame(width: 150, height: 150)
                    .padding()
                    .foregroundColor(.green)

                GeometryReader { geometry in
                    VStack {
                        EmptyView()

                    }.preference(key: WidthPreferenceKey.self, value: [geometry.size.width])
                }
                .frame(height: 0)

                ForEach(self.items()) { item in
                    Text(item.value)
                        .frame(width: self.width / CGFloat(2), height: self.width / CGFloat(2))
                        .background(Color.red)
                }
            }
            .padding()
        }
        .onPreferenceChange(WidthPreferenceKey.self) { value in
            self.width = value[0]
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


是这样做的唯一方法,还是有一种更优雅,更轻松的方法?

1 个答案:

答案 0 :(得分:0)

首先,由于您使用的是VStack,这意味着您需要垂直滚动。因此,您需要设置maxHeight属性,而不是maxWidth。

在第一种方法中,将for..loop包裹在GeometryReader中,然后再包裹在VStack中。因此,直到构建GeometryReader及其子级之前,SwiftUI才认识到您希望这些项垂直堆叠。因此,一个好的解决方法是将GeometryReader放在scrollView之前的第一个块中,以使其起作用:

import SwiftUI

struct Item: Identifiable {
    var id = UUID().uuidString
    var value: String
}

struct ContentView: View {

    func items() -> [Item] {
        var items = [Item]()
        for i in 0..<100 {
            items.append(Item(value: "Item #\(i)"))
        }
        return items
    }

    var body: some View {
        GeometryReader { geometry in
        ScrollView {
            VStack {

                Image(systemName: "circle.fill")
                    .resizable()
                    .scaledToFill()
                    .frame(width: 150, height: 150)
                    .padding()
                    .foregroundColor(.green)

                    ForEach(self.items()) { item in
                        Text(item.value)
                            .frame(width: geometry.size.width / CGFloat(2), height: geometry.size.width / CGFloat(2))
                            .background(Color.red)
                    }
            }
            .frame(maxHeight: .infinity)
            .padding()
        }
        }
        .background(Color.blue)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

第二种方法的问题是,将无限高的VStack包装在不一定具有无限高的GeometryReader中。此外,GeometryReader是一个SwiftUI块,用于从外部块读取尺寸,而不是从其子级获取尺寸。

通常来说,如果要使用当前正在制作的主要SwiftUI组件的Geometry(或设备屏幕,如果它是全屏组件),则将GeometryReader置于其他组件的父级。否则,您需要将GeometryReader组件作为定义良好的尺寸SwiftUI块的唯一子代。

编辑:要添加填充,只需将所需的填充添加到您的scrollView:

ScrollView{
  //...
}
.padding([.leading,.trailing],geometry.size.width / 8)

或仅在一个方向上

ScrollView{
  //...
}
.padding(leading,geometry.size.width / 8) // or any other value, e.g. : 30

如果只需要为for循环中的项目填充,只需将for循环放入另一个VStack中并在其中添加以上填充。