如何使用 LazyVGrid 创建每行的动态 GridRow 宽度?

问题描述 投票:0回答:1

我想做的是创建一个项目网格,其中每行有 1 个项目或 2 个项目,具体取决于是否选择单元格。有一种边缘情况,其中一个单元格可以单独位于一行上,但未被选中,并且它应该只占用与两个网格项共享空间的空间。单元格唯一应占据网格的整个宽度的时间应该是在选择它时。

景色

  • 此视图是我目前的尝试以及我想要修复以使其正常工作的视图。这个问题的主要问题是我定义了一列,然后给它一个有两个的
    GridRow
    ,但是点击似乎不会更新我的状态。
LazyVGrid(columns: [GridItem(.adaptive(minimum: 150)), GridItem(.flexible()) ]) {
    ForEach(locationService.locationGroups, id: \.self) { group in
        
        if let item = group.first(where: { $0.selected }) {
            GridRow {
                TrailDetailCardView(location: item.location)
                    .onTapGesture {
                        locationService.setSelected(for: item, to: !item.selected)
                    }
            }
        } else {
            GridRow {
                ForEach(group, id: \.self) { rowItem in
                    TrailDetailCardView(location: rowItem.location)
                        .onTapGesture {
                            locationService.setSelected(for: rowItem, to: !rowItem.selected)
                        }
                }
            }
        }
        
    }
}

支持数据

  • 列表组在
    @Published var locationGroups: [[LocationToggleable]]
    中进行跟踪,但是
    LocationToggleable
    不是
    Observable
    ,它只包含一个
    selected
    属性,我将其放入其中以使对象正确地
    Hashable
    用于其他用例。我认为这个对象或我的用法就是为什么
    .onTapGesture{...}
    似乎没有更新我的
    LazyVGrid
    中的状态。
static private func getListGroups(filteredLocations: [LocationToggleable]) -> [[LocationToggleable]] {
            var groupedKeys: [[LocationToggleable]] = []
            var currentRow: [LocationToggleable] = []
            
            for item in filteredLocations.sorted(by: { $0.location.locationName < $1.location.locationName }) {
                if item.selected {
                    if !currentRow.isEmpty {
                        groupedKeys.append(currentRow)
                        currentRow = []
                    }
                    
                    groupedKeys.append([item])
                } else {
                    if currentRow.count < 2 {
                        currentRow.append(item)
                    } else {
                        groupedKeys.append(currentRow) // Append the current row to groupedKeys
                        currentRow = [item]// Start a new row with the current item
                    }
                }
            }
            
            if !currentRow.isEmpty {
                groupedKeys.append(currentRow)
            }
            
            return groupedKeys
    }

预期功能

  • 给定一个没有被选择的元素数组,它应该生成 2 个等宽的列,占用尽可能多的空间。
  • 如果某个项目被选中,它应该单独成一行,并且其他网格项目应该移动到上面或下面,具体取决于是否选择了右侧项目或左侧项目。
  • 所选项目之前应该可以有奇数个项目,也可以有偶数个项目。例如,选择左边的项目(不是最后一个)之前会有一个偶数,相反,选择右边的项目(不是最后一个)之前会有一个奇数。

实际功能

  • 上面的代码生成了一个 2 列网格,但是选择不起作用,并且状态似乎没有更新。
  • 下面的代码显示了一个确实有效的解决方案,但效率非常低,因为每次操作我都必须重新计算
    @Published var locationGroups: [[LocationToggleable]]
    ,然后更新视图。

查看有效,但未优化且滞后。

Grid {
                ForEach(locationService.locationGroups, id: \.self) { rowItems in

                    // Check if this row contains a selected item
                    if rowItems.count == 1 && rowItems[0].selected {
                        let selectedItem = rowItems[0]

                        TrailDetailCardView(location: selectedItem.location)
                            .frame(maxWidth: .infinity) // To make the card take the full width
                            .matchedGeometryEffect(id: "location-id-\(selectedItem.id)", in: namespace)
                            .id(selectedItem.id)
                            .onTapGesture {
                                locationService.setSelected(for: selectedItem, to: !selectedItem.selected)
                            }

                    } else {
                        GridRow {
                            ForEach(rowItems, id: \.self) { item in

                                    TrailDetailCardView(location: item.location)
                                        .matchedGeometryEffect(id: "location-id-\(item.id)", in: namespace)
                                        .id(item.id)
                                        .onTapGesture {
                                            locationService.setSelected(for: item, to: !item.selected)
                                        }

                                    if rowItems.count < 2 && !item.selected && locationService.locationGroups.count < 3 {
                                        TrailDetailCardView(location: item.location)
                                            .disabled(true)
                                            .opacity(0.0)
                                    }

                            }
                        }
                    }
                }
            }
            .lineLimit(1)
            .minimumScaleFactor(0.75)
            .padding()

swift user-interface swiftui gridview collections
1个回答
0
投票

解决方案是创建两列,并将

.selected
视为
Section
。特别是将其视为
Section.header
而不仅仅是一个截面体。将来,如果我想要额外的部分,我可以简单地定义它,并将每个部分的其他逻辑保留在正文之外。这并不能解决
@State
问题,但它确实允许延迟加载,从而显着提高性能并满足上述要求。

struct LocationListView: View {
    @Namespace var namespace: Namespace.ID
    @ObservedObject var locationService: LocationService
    
    let gridItems = [
        GridItem(.flexible(minimum: UIScreen.main.bounds.width / 2 - 50)),
        GridItem(.flexible(minimum: UIScreen.main.bounds.width / 2 - 50))
    ]
    
    @State var showingFirst = false
    
    var body: some View {
        
        ScrollViewReader { proxy in
            ScrollView {
                VStack {
                    LazyVGrid(columns: gridItems) {
                        ForEach(locationService.toggleableLocations, id: \.self) { item in
                            if item.selected {
                                Section(content: {}, header: {
                                    TrailDetailCardView(location: item.location)
                                        .frame(maxWidth: .infinity)
                                        .matchedGeometryEffect(id: "location-id-\(item.id)", in: namespace)
                                        .id(item.id)
                                        .onTapGesture {
                                            proxy.scrollTo(item.id)
                                            locationService.setSelected(for: item, to: !item.selected)
                                        }
                                })
                            } else {
                                TrailDetailCardView(location: item.location)
                                    .matchedGeometryEffect(id: "location-id-\(item.id)", in: namespace)
                                    .id(item.id)
                                    .onTapGesture {
                                        proxy.scrollTo(item.id)
                                        locationService.setSelected(for: item, to: !item.selected)
                                    }
                            }
                        }
                    }
                }
                .lineLimit(1)
                .minimumScaleFactor(0.75)
                .padding()
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.