【发布时间】:2021-02-27 10:38:56
【问题描述】:
我有一个应用程序可以按天显示缩略图集合,每一天都是它自己的LazyVGrid,所有的日子都集中在一个VStack。
底线,看起来像在单个ScrollView 中添加多个LazyVGrid 几乎 工作......但没有。
它在滚动时导致不稳定的行为,这里是一个例子:
编辑 4: 我认为我正在取得进展。当网格的最后一行有空间时,这似乎发生了,如果所有行都已满,它似乎按预期工作。
编辑 1: 在此处更改代码以使用 Identifiable(感谢 Simone),但问题仍然存在
import SwiftUI
struct ContentView: View {
var body: some View {
ScrollView {
VStack {
Grid(400, color: .yellow)
Grid(300, color: .blue)
Grid(300, color: .red)
}
}
}
}
struct Item: Identifiable {
var id: UUID = UUID()
var num: Int
}
struct Grid: View {
@State var items = [Item]()
let itemsCount: Int
let color: Color
init(_ itemsCount: Int, color: Color) {
self.itemsCount = itemsCount
self.color = color
}
var body: some View {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 128.0, maximum: 128.0))],
spacing: 5
) {
ForEach(items) { i in
Text(String(i.num))
.frame(width: 128, height:96)
.background(self.color)
}
}
.onAppear {
for i in 1...itemsCount {
self.items.append(Item(num:i))
}
}
}
}
如果您运行此代码并滚动视图,您可能会在某个时候看到ScrollView 跳来跳去。
在窗口较窄时尤其明显,使列表变长
当然,在ScrollView 内部使用单个LazyVGrid 不会出现问题
我尝试过查看查看次数,结果似乎是随机的,有时它会工作一段时间,有时它会立即重现,但更窄的窗口最终总是会显示问题。
我也尝试删除VStack。
编辑 2:这是正在发生的事情的视觉效果:
编辑 3: 我也可以在 iOS 上重现该问题。
编辑 5: 非常丑陋的黑客尝试修复它。
编辑 6: 完整的代码示例
想法是用虚拟的瓷砖填充剩余的瓷砖,一开始似乎很容易,但是LazyVGrid在GeometryReader中被完全破坏了一个问题。
我发现了这个 hack 并用它来确定需要多少额外的图块 https://fivestars.blog/swiftui/flexible-swiftui.html
struct ContentView: View {
var body: some View {
ScrollView {
VStack {
Grid(400, color: .yellow)
Grid(299, color: .blue)
Grid(299, color: .red)
}
}
}
}
// This extension provides a way to run a closure every time the size changes
private struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {}
}
extension View {
func readSize(onChange: @escaping (CGFloat) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size.width)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
}
struct Item: Identifiable {
var id: UUID = UUID()
var num: Int
}
struct Filler: Identifiable {
var id: UUID = UUID()
}
struct Grid: View {
@State var items = [Item]()
@State var width: CGFloat = 0 // We need to store our view width in a property
let itemsCount: Int
let color: Color
let hSpacing = CGFloat(5) // We are going to store horizontal spacing in a property to use it in different places
init(_ itemsCount: Int, color: Color) {
self.itemsCount = itemsCount
self.color = color
}
var body: some View {
// The sole purpose of this is to update `width` state
GeometryReader { geometryProxy in
Color.clear.readSize(onChange: { s in
self.width = s
})
}
// LazyVGrid start
LazyVGrid(columns: [GridItem(.adaptive(minimum: 128.0, maximum: 128.0),spacing: hSpacing)], spacing: 5) {
ForEach(items) { i in
Text(String(i.num))
.frame(width: 128, height:96)
.background(self.color)
}
// Magic happens here, add Filler tiles
ForEach(self.fillers(items.count)) { i in
Rectangle()
.size(CGSize(width: 128, height: 96))
.fill(Color.gray)
}
}
.onAppear {
for i in 1...itemsCount {
self.items.append(Item(num:i))
}
}
}
// And the last part, `fillers()` returns how many dummy tiles
// are needed to fill the gap
func fillers(_ total: Int) -> [Filler] {
guard width > 0 else {
return []
}
var result = [Filler]()
var columnsf = CGFloat(0)
while (columnsf * 128) + (columnsf - 1) * hSpacing <= width {
columnsf = columnsf+1
}
let columns = Int(columnsf - 1)
//let columns = Int(floor((width - hSpacing) / (128 + hSpacing)))
let lastRowTiles = CGFloat(total).truncatingRemainder(dividingBy: CGFloat(columns))
let c = columns - Int(lastRowTiles)
if (lastRowTiles > 1) {
for _ in 0..<c {
result.append(Filler())
}
}
return result
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
这可能是我曾经做过的最丑陋的黑客攻击之一,我只是将它发布在此页面上,以供那些比我更聪明以实现更好的东西的人在此页面上跌跌撞撞。
【问题讨论】:
标签: macos swiftui scrollview lazyvgrid