ScrollView 无法与 GeometryReader 一起正常工作 [英] ScrollView is not working correctly with GeometryReader

查看:86
本文介绍了ScrollView 无法与 GeometryReader 一起正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在滚动视图和 GeometryReader 方面遇到了一些问题.我想要一个图像下的项目列表.并且每个项目应该有以下宽度和高度:
((整个屏幕的宽度 - 前导填充 - 尾部填充)/2).

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

I have some issues with the scrollview and GeometryReader. I want to have a list of items under an image. And each item should have the following width and height:
((width of the entire screen - leading padding - trailing padding) / 2).

I have tried two approaches for my use case. This is the code structure of my first one:

ScrollView
   - VStack
      - Image
      - GeometryReader
         - ForEach
            - Text

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

I am using the geometry reader to get the width of the VStack as it has a padding and I don't want to have the full width of the scrollview.

但是对于 GeometryReader,UI 上仅显示 ForEach 循环中的最后一项.并且 GeometryReader 只有很小的高度.看截图.

But with the GeometryReader, only the last item from the ForEach loop is shown on the UI. And the GeometryReader has only a small height. See screenshot.

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 和绿色只是图像.

The red color are the items in the ForEach loop. Blue the GeometryReader and green just the image.



ScrollView
   -GeometryReader
      - VStack
         - Image
         - ForEach
            -Text

然后我的 ForEach 循环中的项目正确呈现,但无法再滚动.

Then the items in my ForEach loop are rendered correctly but it's not possible to scroll anymore.

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.我在这里错过了什么吗?

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

How can I archive to have the UI correctly shown. Am I'm missing something here?

I would really appreciate some help.
Thank you.



我找到了一种解决方法,可以使用工作的 scrollView 正确呈现 UI,但这对我来说看起来很糟糕.
我正在使用 PreferenceKey 来解决这个问题.

我在滚动视图中使用几何阅读器,高度为 0.只是为了获取我的 VStack 的宽度.在 PreferenceKeyChange 上,我正在更新一个状态变量并使用它来设置我的项目的宽度和高度.

I found a workaround to have the UI correctly rendered with a working scrollView but that looks quite hacky to me.
I am using a PreferenceKey for this workaround.

I am using the geometryReader inside the scrollview with a height of 0. Only to get the width of my VStack. On preferenceKeyChange I am updating a state variable and using this for my item to set the width and height of it.

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()
    }
}


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


Is this the only way of doing that, or is there a more elegant and easier way to do that?

推荐答案

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

First of all, since you are using a VStack, means you need a vertical scrolling. So, you need to set maxHeight property, instead of maxWidth.

在您的第一种方法中,您将 for..loop 包装在 GeometryReader 中,然后再将其包装在 VStack 中.因此,在构建 GeometryReader 及其子项之前,SwiftUI 不会识别出您希望这些项目处于垂直堆栈中.因此,一个好的解决方法是将 GeometryReader 作为滚动视图之前的第一个块以使其工作:

In your first approach, you are wrapping your for..loop in a GeometryReader before wrapping it in a VStack. So, SwiftUI doesn't recognize that you want those items in a vertical stack until before building GeometryReader and its children. So, a good workaround is to put GeometryReader as the very first block before your scrollView to make it work:

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 块,用于从外部块读取尺寸,而不是从其子块中获取尺寸.

The issue with your second approach, is that you are wrapping your infinite-height VStack in a GeometryReader, which is not necessarily has an infinite height. Besides, GeometryReader is a SwiftUI block to read the dimensions from outer block, rather than getting dimensions from its children.

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

Generally speaking, if you want to use Geometry of current main SwiftUI component which you are making (or the device screen, if it's a fullscreen component), then put GeometryReader at a parent level of other components. Otherwise, you need to put the GeometryReader component as the unique child of a well-defined dimensions SwiftUI block.

要添加填充,只需将所需的填充添加到您的滚动视图:

Edited: to add padding, simply add required padding to your 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 中并在那里添加上面的填充.

If you need padding just for items in for loop, simply put the for loop in another VStack and add above padding there.

这篇关于ScrollView 无法与 GeometryReader 一起正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆