ios - ScrollView 无法与 GeometryReader 一起正常工作
问题描述
我对滚动视图和 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。我在这里遗漏了什么吗?
我真的很感激一些帮助。
谢谢你。
编辑
我找到了一种解决方法,可以让 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()
}
}
这是这样做的唯一方法,还是有更优雅和更简单的方法来做到这一点?
解决方案
首先,由于您使用的是 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 中并在那里添加上面的填充。