【问题标题】:Add multiple LazyVGrid to ScrollView将多个 LazyVGrid 添加到 ScrollView
【发布时间】: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: 完整的代码示例

想法是用虚拟的瓷砖填充剩余的瓷砖,一开始似乎很容易,但是LazyVGridGeometryReader中被完全破坏了一个问题。

我发现了这个 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


    【解决方案1】:

    您的 Grid 是惰性的,因此不会一次加载所有项目。您在 foreach 中的 ID 不是唯一的,并且与您之前在 Grid 中的整数冲突。 构建类似可识别结构的东西

        struct SwView: View {
            var body: some View {
           
                ScrollView {
                    Grid(100, color: .yellow)
                    Grid(200, color: .blue)
                    Grid(200, color: .red)
                }
                
        }
    }
    
    struct Item: Identifiable {
        var id: UUID = UUID()
        var num: Int
    }
    
    struct Grid: View {
        
        @State var items: [Item] = []
        var color: Color = .white
        let itemsCount: Int
        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(color)
                    }
                }
            .onAppear() {
                for i in 1...itemsCount {
                    self.items.append(Item(num: i))
                }
                print(items.count)
            }
        }
    }
    

    【讨论】:

    • 谢谢,您说得有道理,但是在修复 Identifiable 之后,问题实际上是相同的,也使用您的确切代码。
    • @YannBizeul 也许我不明白你的问题,检查添加的 gif,正在向右滚动,上传你的问题
    • 问题是 macOS,也许这个问题在 iOS 中不存在?
    • 哦,我没有在 macOS 上查看
    • 我想我找到了一些东西。当最后一行未满时似乎会发生这种情况,因为您正在显示两列并且有偶数个瓷砖,您看不到它。将 200 更改为 199,您也应该复制它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多