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

时间:2021-05-11 00:50:36

标签: listview swiftui swiftui-list

在待办事项列表中,更新列表有两个问题。

  1. 当删除List中的item时,它会飞到随机的地方。 (故障)。我想这与独特的 identifier

  2. 勾选列表中的项目时,它不会更改该部分。视图仅在添加新项目后更新。

  • 此外,也许您知道如何在没有更多项目时隐藏部分,并在添加任务时重新出现。

enter image description here enter image description here

列表视图

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
}

单元格的可重用视图

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)
    }
}

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
        
    }
}

ListViewModel

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
}

1 个答案:

答案 0 :(得分:1)

首先,您添加了错误的 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)
    }
}

注意:第一部分不需要两个 .onDelete。


注意: 如果您仍然面临动画问题,请使用主队列。

.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.
相关问题