1

我对滚动视图和 GeometryReader 有一些问题。我想在图像下有一个项目列表。每个项目应具有以下宽度和高度:
((width of the entire screen - leading padding - trailing padding) / 2).

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

方法#1

ScrollView
   - VStack
      - Image
      - GeometryReader
         - ForEach
            - Text

我正在使用几何阅读器来获取 VStack 的宽度,因为它有一个填充,我不想拥有滚动视图的整个宽度。

但是使用 GeometryReader,只有 ForEach 循环中的最后一项显示在 UI 上。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 和绿色只是图像。

截屏



方法#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()
    }
}

截图No2

如何归档以正确显示 UI。我在这里遗漏了什么吗?

我真的很感激一些帮助。
谢谢你。



编辑

我找到了一种解决方法,可以让 UI 使用正常工作的 scrollView 正确呈现,但这对我来说看起来很 hacky。
我正在为此解决方法使用 PreferenceKey。

我在滚动视图中使用几何读取器,高度为 0。只是为了获得我的 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()
    }
}


这是这样做的唯一方法,还是有更优雅和更简单的方法来做到这一点?

4

1 回答 1

1

首先,由于您使用的是 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{
  //...
}
.padding([.leading,.trailing],geometry.size.width / 8)

或仅在一个方向:

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

如果您只需要对 for 循环中的项目进行填充,只需将 for 循环放在另一个 VStack 中并在那里添加上面的填充。

于 2020-05-05T04:38:43.250 回答