在 ScrollView 中使用 GeometryReader 的 SwiftUI 布局

Posted

技术标签:

【中文标题】在 ScrollView 中使用 GeometryReader 的 SwiftUI 布局【英文标题】:SwiftUI layout with GeometryReader inside ScrollView 【发布时间】:2020-02-03 18:32:50 【问题描述】:

我正在尝试创建一个可滚动的项目网格。我创建了一个名为GridView 的自定义视图,它使用GeometryReader 将空间划分为HStacksVStacks 中的列和行。但由于某种原因,当在ScrollView 中时,它的大小几乎缩小到零。在屏幕截图中,您看到 GridView(红色)和它的父 VStack(绿色)缩小了。 内部网格的项目仍然可见,呈现在其区域之外,但无法正确滚动。

为什么GridView 的大小不是容纳其项目所需的大小?如果是这样,我认为这个 UI 会正常滚动。

struct ContentView: View 
    var body: some View 
        ScrollView 
            VStack 
                Text("Section 1")
                GridView(columns: 2, items: [1, 3, 5, 7, 9, 11, 13, 15])  num in
                    Text("Item \(num)")
                
                .background(Color.red.opacity(0.2))
            .background(Color.green.opacity(0.2))
        .background(Color.blue.opacity(0.2))
    


struct GridView<Content>: View where Content: View 
    var columns: Int
    let items: [Int]
    let content: (Int) -> Content

    init(columns: Int, items: [Int], @ViewBuilder content: @escaping (Int) -> Content) 
        self.columns = columns
        self.items = items
        self.content = content
    
    var rowCount: Int 
        let (q, r) = items.count.quotientAndRemainder(dividingBy: columns)
        return q + (r == 0 ? 0 : 1)
    
    func elementFor(_ r: Int, _ c: Int) -> Int? 
        let i = r * columns + c
        if i >= items.count  return nil 
        return items[i]
    
    var body: some View 
        GeometryReader  geo in
            VStack 
                ForEach(0..<self.rowCount)  ri in
                   HStack 
                        ForEach(0..<self.columns)  ci in
                            Group   
                                if self.elementFor(ri, ci) != nil 
                                    self.content(self.elementFor(ri, ci)!)
                                        .frame(width: geo.size.width / CGFloat(self.columns),
                                               height: geo.size.width / CGFloat(self.columns))
                                 else 
                                    Text("")
                                
                            
                        
                    
                
            
        
    

【问题讨论】:

我用最新的 xcode 和 ios 测试了你的代码,它为我滚动。不可滚动是什么意思? 我的意思是我看不到项目13和15,因为ScrollView认为GridView只是红色区域的大小(10pt高),所以它没有滚动,因为它认为它没有没必要。屏幕上的红色区域(GridView)有多大?它应该包含项目。 哦,我明白了,我在更大屏幕的 iPhone 上看着它,它看起来好像在滚动。但它没有,当你的滚动视图中没有元素时,它只会滚动最低限度 我在使用 GeometryReader 时遇到了同样的问题。根据this article,我尝试使用fixedSize,但我没有运气。 【参考方案1】:

GeometryReader 是一个容器视图,它将其内容定义为其自身大小和坐标空间的函数。将灵活的首选大小返回为其 父布局。

struct ContentView: View 
    var body: some View 
        ScrollView 
            VStack 
                Text("Section 1")
                GridView(columns: 2, items: [1, 3, 5, 7, 9, 11, 13, 15])  num in
                    Text("Item \(num)")
                
                .background(Color.red.opacity(0.5))
            .background(Color.green.opacity(0.2))
        .background(Color.blue.opacity(0.2))
    

GridView 的高度是多少? 嗯...行数乘以网格单元格的高度,也就是GridView的高度除以行数...

方程没有解:-)

是的,我可以看到,您将网格单元格的高度定义为 GridView 的宽度除以列数,但 SwiftUI 并不那么聪明。

您必须计算 GridView 的高度。我将行数固定为 4 以简化它...

struct ContentView: View 
    var body: some View 
        GeometryReader  g in
        ScrollView 
            VStack 
                Text("Section 1")
                GridView(columns: 2, items: [1, 3, 5, 7, 9, 11, 13, 15])  num in
                    Text("Item \(num)")
                .frame(height: g.size.width / 2 * CGFloat(4))
                .background(Color.red.opacity(0.5))
            .background(Color.green.opacity(0.2))
        .background(Color.blue.opacity(0.2))
        
    

您最好从 GridView.body 中删除几何读取器和网格单元格框架,并在内容视图中设置单元格的大小。

struct ContentView: View 
    var body: some View 
        GeometryReader  g in
        ScrollView 
            VStack 
                Text("Section 1")
                GridView(columns: 2, items: [1, 3, 5, 7, 9, 11, 13, 15])  num in
                    Text("Item \(num)").frame(width: g.size.width / 2, height: g.size .width / 2)
                
                .background(Color.red.opacity(0.5))
            .background(Color.green.opacity(0.2))
        .background(Color.blue.opacity(0.2))
        
    

如您所见,GridView 的高度无需定义,方程现在有了解。

【讨论】:

我不明白你在说什么“方程”没有解决方案。它有一个简单的解决方案。在我的设备上,GeometryReader 传递的宽度为 320,因此如果您将 20 个项目(例如)放入数组中,则 GridView 中内容的高度为 320/nCols * nRows = 320/2*10 = 1600. 你可以通过跳过整个计算来证明这不是原因。相反,只需将frame(height: 1600) 直接硬编码在GeometryReader 内的VStack 上。它仍然不会布局正确。 [继续下一条评论] ...所以问题是GeometryReader 内的VStack 的高度没有用于GridView 的高度。没有人(包括 Apple DTS)能够解释原因,所以我认为这是 SwiftUI 中的一个错误。有一种使用首选项将高度传递回视图层次结构的解决方法,但它太丑了,我几乎更喜欢做你所做的事情并从GridView 之外设置高度。不过,作为一个长期解决方案,这太可怕了。 SwiftUI 需要允许自定义 View 计算自己的高度,即使它们位于 ScrollView 中。 @RobN 非常相似的问题和解释***.com/questions/60474057/… @RobN 这个想法是子视图必须决定它的大小,GeometryReader 为您提供可用的最大大小。 ScrollView 的最大尺寸是多少?这就是为什么我写这个方程没有解。 ScrollView 中的 VStack .. VStack 的大小是多少?等等... @RobN 看到,在我的回答中,GeometryReader 为您提供了 ScrollView 父级中可用空间的大小。这是“固定”值,您可以从中进行计算。

以上是关于在 ScrollView 中使用 GeometryReader 的 SwiftUI 布局的主要内容,如果未能解决你的问题,请参考以下文章

MySQL中地理位置数据扩展geometry的使用心得

我可以在线程中使用 Boost.Geometry.index.rtree 吗?

mysql中geometry类型的简单使用

ArcMap中使用ArcPy实现Geometry与WKT的相互转换

VS2010使用boost::geometry库时为什么编译不通过说明

扩展mybatis和通用mapper,支持mysql的geometry类型字段