使用 LazyVGrid 时,DetailView 仅调用数组中的第一项

DetailView only calls on first item in array when using LazyVGrid

我有一个可以添加项目的列表,当我点击它们时,它打开了正确的详细视图。我最近将列表换成 LazyVGrid,但是当我单击网格项时,详细视图仅调用数组中的第一项。

这是列表视图:

import SwiftUI

struct ListView: View {

    @EnvironmentObject var listViewModel: ListViewModel
    @State var addItemView = false
    @State var detailView = false
    
    let columns = [GridItem(.flexible()), GridItem(.flexible())]

    var body: some View {
        NavigationView {
            ZStack {
                ZStack {
                    Color(.white)
                }
                VStack {
                    Button(action: {
                        self.addItemView = true
                    }, label: {
                        Text("Add")
                    }).sheet(isPresented: $addItemView, content: {
                        AddItemView()
                    })
                    LazyVGrid(columns: columns, spacing: 20) {  // <-- the only line i comment when using list
//                        List {
                        ForEach(listViewModel.item, id:\.id){ item in
                            Button(action: {
                                self.detailView = true
                            }, label: {
                                ListRowView(item: item)
                            }).sheet(isPresented: $detailView, content: {
                                DetailView(item: item)
                            })
                        }
//                            .onDelete(perform: listViewModel.deleteItem)
//                            .onMove(perform: listViewModel.moveItem)
//                            .listRowBackground(Color.blue)
                    }
//                        .listStyle(PlainListStyle())
                    Spacer()
                }
            }
        }
    }
}

struct ListRowView: View {   // <-- using this as grid item
    @State var item:Item

    var body: some View{
        VStack {
            Text(item.name).foregroundColor(.white)
        }.frame(width: 150, height: 150, alignment: .center)
        .background(Color.blue)
        .cornerRadius(10)
    }
}

添加项目和详情视图:

struct AddItemView: View {
    @Environment(\.presentationMode) var presentationMode
    @EnvironmentObject var listViewModel: ListViewModel
    @State var id = UUID()
    @State var name = ""

    var body: some View {
        TextField("Enter Name", text: $name)
        Button(action: {
            addItem()
        }, label: {
            Text("Done")
        })
    }

    func addItem() {
        listViewModel.addItem(id: id.uuidString, name: name)
        presentationMode.wrappedValue.dismiss()
    }
}

struct DetailView: View {
    @State var item:Item

    var body: some View {
        Text(item.name)
    }
}

这就是我添加每个项目的方式:

import Foundation

struct Item: Hashable, Codable, Equatable {
    var id:String
    var name: String
}

class ListViewModel: ObservableObject {

    @Published var item: [Item] = [] {
        didSet {
            saveItem()
        }
    }

    let itemsKey: String = "items_key"

    init() {
        getItems()
    }

    func getItems() {
        guard
            let data = UserDefaults.standard.data(forKey: itemsKey),
            let savedItems = try? JSONDecoder().decode([Item].self, from: data)
        else { return }
    
        self.item = savedItems
    }

    func deleteItem(indexSet: IndexSet){
        item.remove(atOffsets: indexSet)
    }

    func moveItem(from: IndexSet, to: Int){
        item.move(fromOffsets: from, toOffset: to)
    }

    func addItem(id: String, name: String){
        let newItem = Item(id: id, name: name)
        item.append(newItem)
        print(newItem)
    }

    func saveItem() {
        if let encodedData = try? JSONEncoder().encode(item) {
            UserDefaults.standard.set(encodedData, forKey: itemsKey)
        }
    }
}

我不确定为什么 LazyVGrid 只调用第一项,如有任何帮助,我们将不胜感激

问题在这里:

@State var detailView = false /// will only work for 1 sheet, not multiple...

...

ForEach(listViewModel.item, id:\.id){ item in
    Button(action: {
        self.detailView = true
    }, label: {
        ListRowView(item: item)
    }).sheet(isPresented: $detailView, content: { /// not good to have `sheet` inside ForEach
        DetailView(item: item)
    })
}

ForEach 的每次迭代中,您都有一个 sheet。有很多 sheet... 当 detailView 设置为 true 时,它​​们都会尝试呈现。机缘巧合,第一个出现了。

相反,您需要使用 sheet(item:onDismiss:content:)sheet 的这个替代版本是专门为您的目的而制作的——当您有一个 Item 的数组并且想要为特定的 Item.[=30 呈现一个 sheet 时=]

首先,您需要使 Item 符合 Identifiable

                                           /// here!
struct Item: Hashable, Codable, Equatable, Identifiable {

然后,将旧触发器 @State var detailView = false 替换为 @State var selectedDetailItem: Item?。另外,确保将 sheet 移到 ForEach 之外,这样它就不会重复。现在,sheet 只会在 selectedDetailItem 不为零时出现。

struct ListView: View {

    @EnvironmentObject var listViewModel: ListViewModel
    @State var addItemView = false
    @State var selectedDetailItem: Item? /// here!
    
    let columns = [GridItem(.flexible()), GridItem(.flexible())]

    var body: some View {
        NavigationView {
            ZStack {
                ZStack {
                    Color(.white)
                }
                VStack {
                    Button(action: {
                        self.addItemView = true
                    }, label: {
                        Text("Add")
                    }).sheet(isPresented: $addItemView, content: {
                        AddItemView()
                    })
                    LazyVGrid(columns: columns, spacing: 20) {
                        ForEach(listViewModel.item, id:\.id){ item in
                            Button(action: {
                                self.selectedDetailItem = item /// set the selected item
                            }, label: {
                                ListRowView(item: item)
                            })
                        }
                    }
                    Spacer()
                }
            }
        } /// present the sheet here
        .sheet(item: $selectedDetailItem) { selectedItem in
            DetailView(item: selectedItem)
        }
    }
}

结果: