【问题标题】:ScrollView is not working correctly with GeometryReaderScrollView 无法与 GeometryReader 一起正常工作
【发布时间】:2020-08-19 12:52:49
【问题描述】:

我对滚动视图和 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()
    }
}

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

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



编辑

我找到了一种解决方法,可以使用正常工作的 scrollView 正确呈现 UI,但对我来说这看起来很 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()
    }
}


这是唯一的方法吗,还是有更优雅、更简单的方法?

【问题讨论】:

    标签: ios swift swiftui scrollview geometryreader


    【解决方案1】:

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

    在您的第一种方法中,您将 for..loop 包装在 GeometryReader 中,然后再将其包装在 VStack 中。因此,在构建 GeometryReader 及其子项之前,SwiftUI 不会识别出您希望将这些项目放在垂直堆栈中。因此,一个好的解决方法是将 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 {
            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 中并在那里添加上面的填充。

    【讨论】:

    • 感谢您的回答。问题是,在您的情况下,geometryReaders size.width 等于整个屏幕的宽度。但我需要 for each 循环中的项目(带有 - 前导填充 - 尾随填充的屏幕)。知道如何存档吗?
    • @stefOCDP。已编辑。这就是你要找的吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-24
    • 1970-01-01
    • 2017-12-14
    • 2013-02-06
    • 2019-02-02
    • 2021-10-15
    • 2016-07-05
    相关资源
    最近更新 更多