Listview 更新和删除列表中的项目,SwiftUI

Listview 更新和删除列表中的项目,SwiftUI,listview,swiftui,swiftui-list,Listview,Swiftui,Swiftui List,在待办事项列表中,我在更新列表时遇到两个问题 删除列表中的项目时,它会飞到随机位置。(油嘴滑舌)。我想这是关于使用uniqueidentifier 勾选列表中的项目时,不会更改节。视图仅在添加新项目后更新 此外,也许您知道如何在不再有项目时隐藏分区,并在添加任务时重新显示 列表视图 struct ContentView: View { @ObservedObject var listViewModel = ListViewModel() @Stat

在待办事项列表中,我在更新列表时遇到两个问题

  • 删除列表中的项目时,它会飞到随机位置。(油嘴滑舌)。我想这是关于使用unique
    identifier

  • 勾选列表中的项目时,不会更改节。视图仅在添加新项目后更新

    • 此外,也许您知道如何在不再有项目时隐藏分区,并在添加任务时重新显示

    列表视图

    struct ContentView: View {
        
        @ObservedObject var listViewModel = ListViewModel()
        
        @State var newItem = ""
            
        var body: some View {
            
            VStack {
                Form {
                    // To-do section
                    Section(header: Text("New")) {
                        ForEach(listViewModel.itemCellViewModels) { itemCellViewModel in
                            
                            if itemCellViewModel.item.accomplished == false {
                                
                                ItemCellView(itemCellViewModel: itemCellViewModel)
                            }
                            
                        }.onDelete(perform: { indexSet in
                            //remove item from the shopping list
                            self.listViewModel.itemCellViewModels.remove(atOffsets: indexSet)
                            print(indexSet)
                        })
                        .onDelete(perform: listViewModel.removeRows)
                    }
                    
                    // Accomplished section
                    Section(header: Text("Done")) {
                        ForEach(listViewModel.itemCellViewModels.indices, id: \.self) { index in
                            if listViewModel.itemCellViewModels[index].item.accomplished == true {
                                ItemCellView(itemCellViewModel: listViewModel.itemCellViewModels[index])
                            }
                        }.onDelete(perform: listViewModel.removeRows)
                        
                    }
                }
                
                
                
                TextField("Enter item here", text: $newItem) { _ in
                    
                } onCommit: {
                    self.listViewModel.addItem(item: Item(productName: newItem, accomplished: false))
                    newItem = ""
                }
                .autocapitalization(.none)
                .padding()
                .border(Color.blue)
                .padding()
            }
            // update ViewModel with placeholder data
            .onAppear(perform: {self.listViewModel.itemCellViewModels.append(contentsOf: placeholderItems)})
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
    var placeholderItems = [
        ItemViewModel(item: Item(productName: "1", accomplished: false)),
        ItemViewModel(item: Item(productName: "2", accomplished: false)),
        ItemViewModel(item: Item(productName: "3", accomplished: false)),
        ItemViewModel(item: Item(productName: "4", accomplished: false)),
        ItemViewModel(item: Item(productName: "5", accomplished: false)),
        ItemViewModel(item: Item(productName: "6", accomplished: true)),
        ItemViewModel(item: Item(productName: "7", accomplished: true)),
        ItemViewModel(item: Item(productName: "8", accomplished: true)),
        ItemViewModel(item: Item(productName: "9", accomplished: true)),
        ItemViewModel(item: Item(productName: "10", accomplished: true))
    ]
    
    /// The rest of the code
    
    // Reusable View for Cells
    struct ItemCellView: View {
        @ObservedObject var itemCellViewModel: ItemViewModel
        
        // send an item (doesn't return anything)
        var onCommit: (Item) -> (Void) = { _ in }
        
        var body: some View {
            HStack {
                VStack(alignment: .leading) {
                    Text(itemCellViewModel.item.productName)
                }
                Spacer()
                
                Button(action: {
                    
                    itemCellViewModel.item.accomplished.toggle()
                    
                }, label: {
                    Image(systemName: (itemCellViewModel.item.accomplished ? "checkmark.square" : "square"))
                        .resizable()
                        .frame(width: 25, height: 25)
                })
            }.opacity(itemCellViewModel.item.accomplished ? 0.3 : 1)
        }
    }
    
    import Foundation
    import Combine
    
    class ItemViewModel: ObservableObject, Identifiable {
        
        @Published var item: Item
        
        var id = UUID()
        @Published var items: [Item] = []
        @Published var completionStateIconName = ""
        
        private var cancellables = Set<AnyCancellable>()
        
        init(item: Item) {
            self.item = item
            
            $item
                .map { item in
                    item.accomplished ? "checkmark.square" : "square"
                }
                .assign(to: \.completionStateIconName, on: self)
                .store(in: &cancellables) // <- for memory management purposes
            
        }
    }
    
    class ListViewModel: ObservableObject {
        
        @Published var itemCellViewModels = [ItemViewModel]()
        
        private var cancellables = Set<AnyCancellable>()
        
        func addItem(item: Item) {
            let itemVM = ItemViewModel(item: item)
            self.itemCellViewModels.append(itemVM)
        }
        
        func removeRows(at offsets: IndexSet) {
            itemCellViewModels.remove(atOffsets: offsets)
        }
    }
    
    // Model
    struct Item: Identifiable {
        var id = UUID()
        var productName: String
        var accomplished: Bool
    }
    
    
    ItemViewModel

    import Foundation
    import Combine
    
    class ItemViewModel: ObservableObject, Identifiable {
        
        @Published var item: Item
        
        var id = UUID()
        @Published var items: [Item] = []
        @Published var completionStateIconName = ""
        
        private var cancellables = Set<AnyCancellable>()
        
        init(item: Item) {
            self.item = item
            
            $item
                .map { item in
                    item.accomplished ? "checkmark.square" : "square"
                }
                .assign(to: \.completionStateIconName, on: self)
                .store(in: &cancellables) // <- for memory management purposes
            
        }
    }
    
    import Foundation
    import Combine
    
    class ListViewModel: ObservableObject {
        
        @Published var itemCellViewModels = [ItemViewModel]()
        
        private var cancellables = Set<AnyCancellable>()
        
        func addItem(item: Item) {
            let itemVM = ItemViewModel(item: item)
            self.itemCellViewModels.append(itemVM)
        }
        
        func removeRows(at offsets: IndexSet) {
            itemCellViewModels.remove(atOffsets: offsets)
        }
    }
    
    struct Item: Identifiable {
        var id = UUID()
        var productName: String
        var accomplished: Bool
    }
    

    首先,您添加了错误的
    ForEach
    。 因为您只需要添加那些已完成或未完成的对象。您需要使用过滤器,而不是迭代所有对象并检查ForEach中的条件。通过删除此选项,您的第一个问题将解决动画问题,您还可以添加一个空条件,以便在完成的阵列为空时,该部分会自动将其从视图中删除

    作为下面的演示,我们对两个部分使用了不同的数组,因此节行的索引将发生变化,并且您的数据源是单个数组。为此,需要按id从数组中删除该项

    struct ContentView: View {
        
        @ObservedObject var listViewModel = ListViewModel()
        
        var arrNotAccomplished: [ItemViewModel] {
            return listViewModel.itemCellViewModels.filter({!$0.item.accomplished})
        }
        var arrAccomplished: [ItemViewModel] {
            return listViewModel.itemCellViewModels.filter({$0.item.accomplished})
        }
        
        @State var newItem = ""
            
        var body: some View {
            
            VStack {
                Form {
                       // To-do section
                    if !arrNotAccomplished.isEmpty {
                        Section(header: Text("New")) {
                            ForEach(arrNotAccomplished) { itemCellViewModel in
                                ItemCellView(itemCellViewModel: itemCellViewModel) { (_) -> (Void) in
                                    self.listViewModel.objectWillChange.send()
                                }
                            }.onDelete { (index) in
                                guard let firstIndex = index.first else { return }
                                self.listViewModel.removeRows(for: arrNotAccomplished[firstIndex].id)
                            }
                        }
                    }
                       // Accomplished section
                       if !arrAccomplished.isEmpty {
                           Section(header: Text("Done")) {
                               ForEach(arrAccomplished) { itemCellViewModel in
                                   ItemCellView(itemCellViewModel: itemCellViewModel) { (_) -> (Void) in
                                       self.listViewModel.objectWillChange.send()
                                   }
                               }.onDelete { (index) in
                                guard let firstIndex = index.first else { return }
                                self.listViewModel.removeRows(for: arrAccomplished[firstIndex].id)
                            }
                           }
                       }
                   }
                
    //            ====== other body view code =========
    
    更新了删除函数代码

    func removeRows(for id: UUID) {
            self.itemCellViewModels.removeAll(where: {$0.id == id})
    }
    

    问题2: 当更新内部数组中的数据时,嵌套数组不会影响某些时间。 我使用了提交闭包,并将操作强制发送到主父视图和更新视图

    struct ItemCellView: View {
        @ObservedObject var itemCellViewModel: ItemViewModel
        
        // send an item (doesn't return anything)
        var onCommit: (Item) -> (Void) = { _ in }
        
        var body: some View {
            HStack {
                VStack(alignment: .leading) {
                    Text(itemCellViewModel.item.productName)
                }
                Spacer()
                
                Button(action: {
                    itemCellViewModel.item.accomplished.toggle()
                    onCommit(itemCellViewModel.item) //<-- Here
                }, label: {
                    Image(systemName: (itemCellViewModel.item.accomplished ? "checkmark.square" : "square"))
                        .resizable()
                        .frame(width: 25, height: 25)
                })
            }.opacity(itemCellViewModel.item.accomplished ? 0.3 : 1)
        }
    }
    

    为了提高可读性,您会将
    productName
    更改为什么?mb
    项目。
    <代码>值/
    名称
    /
    说明
    ?我建议
    名称
    谢谢!添加了
    withAnimation{onCommit(itemCellViewModel.item)}
    @MikeMaus这个答案有什么问题?删除单元格仍然是一个问题,例如,尝试删除第7行,它会删除其他元素。@MikeMaus我忽略了这一点。请检查更新的Answare。它工作得很好!我以前尝试过以下
    ForEach(arrnotaccompleted,id:\.self.item.id)
    认为它应该通过密钥路径中提到的id删除。这没有成功。
    struct ItemCellView: View {
        @ObservedObject var itemCellViewModel: ItemViewModel
        
        // send an item (doesn't return anything)
        var onCommit: (Item) -> (Void) = { _ in }
        
        var body: some View {
            HStack {
                VStack(alignment: .leading) {
                    Text(itemCellViewModel.item.productName)
                }
                Spacer()
                
                Button(action: {
                    itemCellViewModel.item.accomplished.toggle()
                    onCommit(itemCellViewModel.item) //<-- Here
                }, label: {
                    Image(systemName: (itemCellViewModel.item.accomplished ? "checkmark.square" : "square"))
                        .resizable()
                        .frame(width: 25, height: 25)
                })
            }.opacity(itemCellViewModel.item.accomplished ? 0.3 : 1)
        }
    }
    
    .onDelete(perform: { (indexSet) in
       DispatchQueue.main.async {
          listViewModel.removeRows(at: indexSet)
       }
    })
    
    Even in your code delete the item from the array with some delay it will work.