在 ScrollView 中使用 GeometryReader 的 SwiftUI 布局
Posted
技术标签:
【中文标题】在 ScrollView 中使用 GeometryReader 的 SwiftUI 布局【英文标题】:SwiftUI layout with GeometryReader inside ScrollView 【发布时间】:2020-02-03 18:32:50 【问题描述】:我正在尝试创建一个可滚动的项目网格。我创建了一个名为GridView
的自定义视图,它使用GeometryReader
将空间划分为HStacks
和VStacks
中的列和行。但由于某种原因,当在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 布局的主要内容,如果未能解决你的问题,请参考以下文章
我可以在线程中使用 Boost.Geometry.index.rtree 吗?
ArcMap中使用ArcPy实现Geometry与WKT的相互转换