无法在部分列表视图中编辑/删除正确的项目

Posted

技术标签:

【中文标题】无法在部分列表视图中编辑/删除正确的项目【英文标题】:Unable to edit/delete correct item into Section list view 【发布时间】:2021-04-28 13:23:55 【问题描述】:

我在下面的视图中有一个包含已完成/未完成部分的任务列表,但是当尝试编辑列表中的一项时,始终选择该部分的最后一项,甚至不允许删除。它在测试该部分内的所有记录都是一条记录时看起来,因此删除幻灯片不起作用,并且在编辑它时会获取最后一条记录。下面是完整的代码,以便您尝试帮助我确定问题所在。

import SwiftUI
import CoreData
import UserNotifications

struct ListView: View 
    
    @Environment(\.presentationMode) var presentationMode
    @Environment(\.managedObjectContext) var viewContext
    
    @FetchRequest(fetchRequest: Task.taskList(),animation: .default) private var items: FetchedResults<Task>
    
    @State var isAddFormPresented: Bool = false
    
    @State var taskToEdit: Task?
    
    init(predicate: NSPredicate?, sortDescriptor: NSSortDescriptor) 
        let fetchRequest = NSFetchRequest<Task>(entityName: Task.entity().name ?? "Task")
        fetchRequest.sortDescriptors = [sortDescriptor]
        
        if let predicate = predicate 
            fetchRequest.predicate = predicate
        
        _items = FetchRequest(fetchRequest: fetchRequest)
        
        UITableViewCell.appearance().backgroundColor = .white
        UITableView.appearance().backgroundColor = .white
        
    
    
    var body: some View 
        
        let data = groupedEntries(self.items)
        
        VStack(alignment: .center) 
            
            if data.isEmpty 
                
                Spacer()
                
                Image("empty")
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: 250, height: 250)
                    .clipShape(Circle())
                    .overlay(Circle().stroke(Color.pink, lineWidth: 2))
                
                Spacer()
                
             else 
                
                List 
                    
                    ForEach(data, id: \.self)  (section: [Task]) in
                        
                        Section(header: Text(section[0].isComplete == false ? "Incomplete" : "Completed")
                                    .font(.body)
                                    .foregroundColor(section[0].isComplete == false ? Color.pink : Color.green)
                        )
                            
                            self.completedView(section: section)
                            
                        
                        .sheet(item: $taskToEdit, onDismiss: 
                            self.taskToEdit = nil
                        )  task in
                            TaskFormView(
                                taskToEdit: task,
                                name: task.name!,
                                taskDetails: task.taskDetails ?? "",
                                important: TaskType2(rawValue: task.important ?? "") ?? .none,
                                urgent: TaskType(rawValue: task.urgent ?? "") ?? .none,
                                secondaryCategory: Category(rawValue: task.secondaryCategory ?? "") ?? .other,
                                isComplete: task.isComplete,
                                dateAdded: task.dateAdded ?? Date()
                            )
                        
                    
                
                .listStyle(SidebarListStyle())
                
                HStack 
                    
                    Spacer()
                    
                    Button(action: addTapped) 
                        Image(systemName: "plus.circle.fill")
                            .resizable()
                            .frame(width: 30, height: 30)
                            .shadow(radius: 20)
                    
                    .padding(.trailing, 40)
                    .padding(.bottom, 24)
                    .accentColor(Color(UIColor.systemRed))
                
            
        
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color(UIColor.systemYellow).opacity(0.5))
        .edgesIgnoringSafeArea(.all)
        .sheet(isPresented: $isAddFormPresented) 
            
            TaskFormView()
                .environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)
        
    
    
    func groupedEntries(_ result : FetchedResults<Task>) -> [[Task]] 
        
        return Dictionary(grouping: result)  (element : Task) in
            
            element.isComplete
        
        .values.sorted()  $0[0].dateAdded! < $1[0].dateAdded! 
    
    
    func completedView(section: [Task]) -> some View 
        
        ForEach(section, id: \.id)  task in
            
            Button(action: 
                
                taskToEdit = task
                
            ) 
                HStack 
                    
                    CategoryRowView(category: Category(rawValue: task.secondaryCategory!)!, dateAdded: task.dateAdded!)
                    
                    VStack(alignment: .leading) 
                        
                        HStack 
                            
                            if task.isComplete != false 
                                Image(systemName: "checkmark.circle.fill")
                                    .foregroundColor(.pink)
                                    .padding(.top, 10)
                                    .padding(.leading, 5)
                            
                            Text(task.name!)
                                .font(.system(size: 20, weight: .regular, design: .default))
                                .foregroundColor(.primary)
                                .padding(.top, 10)
                                .padding(.leading, 5)
                        
                        Spacer()
                        
                        HStack 
                            
                            Image(systemName: "tag.fill")
                                .foregroundColor(.secondary)
                            Text(task.important == "none" ? "Not Important" : "Important")
                                .font(.system(size: 12, weight: .regular, design: .default))
                                .foregroundColor(.secondary)
                                .padding(.vertical)
                            Text(task.urgent == "none" ? "Not Urgent" : "Urgent")
                                .font(.system(size: 12, weight: .regular, design: .default))
                                .foregroundColor(.secondary)
                                .padding(.vertical)
                        
                        .padding(.leading, 5)
                    
                    .padding(.trailing, 2)
                    Spacer()
                
                .frame(height: 140)
                .background(Color(.systemBackground))
                .cornerRadius(10)
                .shadow(color: .black, radius: 4, x: 0, y: 0)
                .padding(.vertical, 5)
            
        
        .onDelete  row in
            deleteEntry(row: row, in: section)
        
    
    
    func addTapped() 
        
        isAddFormPresented.toggle()
    
    
    private func onReturnTapped() 
        
        self.presentationMode.wrappedValue.dismiss()
    
    
    func deleteEntry(row: IndexSet, in section: [Task]) 
        
        let task = section[row.first!]
        
        UNUserNotificationCenter.current().removeAllDeliveredNotifications()
        
        UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [task.id!.uuidString])
        
        viewContext.delete(task)
        
        do 
            
            try viewContext.save()
            
         catch 
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        
    


private let itemFormatter: DateFormatter = 
    let formatter = DateFormatter()
    formatter.dateFormat = "dd MMM"
    
    return formatter
()

【问题讨论】:

谢谢fatihyildizhan :) 你能将 onDelete 修饰符中的行和节打印到控制台吗?我感觉修饰符的位置不正确。 我不能,因为我什至不能滑动记录来激活onDelete来删除记录。 您的代码远不是Minimal Reproducible Example,正文中的分组效率很低,为什么不直接使用NSFetchedResultsController 并让CoreData 完成所有分组/排序工作。此外,您的 onDelete 似乎在部分而不是行上,因此您将获得有关部分的信息而不是任务/行将其向上移动 抱歉无法重现,但我完全分享了我正在处理的代码。我不知道如何在这种情况下使用 NSFetchedResultsController 并且 onDelete 就在构建行的 ForEach 中。 【参考方案1】:

这是您的View 的简化版本,因此您可以了解NSFetchedResultsController 的工作方式。您需要List 才能滑动一行来删除。

import SwiftUI
import CoreData
class TaskListViewModel: ObservableObject 
    let persistenceController = PersistenceController.previewAware()
    @Published var fetchedResultsController: NSFetchedResultsController<Task>?
    
    init() 
        setupController()
    
    func setupController() 
        do
            fetchedResultsController = try retrieveFetchedController(sortDescriptors: nil, predicate: nil, sectionNameKeyPath: #keyPath(Task.isComplete))
        catch
            print(error)
        
    
    func deleteObject(object: Task) 
        persistenceController.container.viewContext.delete(object)
        save()
    
    func save() 
        do 
            if persistenceController.container.viewContext.hasChanges
                try persistenceController.container.viewContext.save()
                objectWillChange.send()
            else
            
         catch 
            print(error)
        
    

//MARK: FetchedResultsController setup
extension TaskListViewModel
    func retrieveFetchedController(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?, sectionNameKeyPath: String) throws -> NSFetchedResultsController<Task> 
        
        return try initFetchedResultsController(sortDescriptors: sortDescriptors, predicate: predicate, sectionNameKeyPath: sectionNameKeyPath)
    
    private func initFetchedResultsController(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?, sectionNameKeyPath: String) throws -> NSFetchedResultsController<Task> 
        fetchedResultsController = getFetchedResultsController(sortDescriptors: sortDescriptors, predicate: predicate, sectionNameKeyPath: sectionNameKeyPath)
        do 
            try fetchedResultsController!.performFetch()
            return fetchedResultsController!
            
         catch 
            print( error)
            throw error
        
    
    func getFetchedResultsController(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?, sectionNameKeyPath: String) -> NSFetchedResultsController<Task> 
        
        return NSFetchedResultsController(fetchRequest: getEntityFetchRequest(sortDescriptors: sortDescriptors, predicate: predicate), managedObjectContext: persistenceController.container.viewContext, sectionNameKeyPath: sectionNameKeyPath, cacheName: nil)
    
    private func getEntityFetchRequest(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) -> NSFetchRequest<Task>
    
        
        let fetchRequest: NSFetchRequest<Task> = Task.fetchRequest()
        fetchRequest.includesPendingChanges = false
        fetchRequest.fetchBatchSize = 20
        if sortDescriptors != nil
            fetchRequest.sortDescriptors = sortDescriptors
        else
            fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Task.dateAdded), ascending: false)]
        
        if predicate != nil
            fetchRequest.predicate = predicate
        
        return fetchRequest
    

struct TaskListView: View 
    @StateObject var vm: TaskListViewModel = TaskListViewModel()
    @State var taskToEdit: Task?
    var body: some View 
        if vm.fetchedResultsController?.sections != nil
            List
                ForEach(0..<vm.fetchedResultsController!.sections!.count)idx in
                    let section = vm.fetchedResultsController!.sections![idx]

                    TaskListSectionView(objects: section.objects as? [Task] ?? [], taskToEdit: $taskToEdit, sectionName: section.name).environmentObject(vm)
                    
                
            .sheet(item: $taskToEdit, onDismiss: 
                vm.save()
            )editingTask in
                TaskEditView(task: editingTask)
            
            
        else
            Image(systemName: "empty")
        
    


struct TaskEditView: View 
    @ObservedObject var task: Task
    var body: some View 
        TextField("name", text: $task.name.bound)
    

struct TaskListSectionView: View 
    @EnvironmentObject var vm: TaskListViewModel
    let objects: [Task]
    @State var deleteAlert: Alert = Alert(title: Text("test"))
    @State var presentAlert: Bool = false
    @Binding var taskToEdit: Task?
    var sectionName: String
    
    var body: some View 
        Section(header: Text(sectionName) , content:  ForEach(objects, id: \.self)obj in
            let task = obj as Task
            Button(action: 
                taskToEdit = task
            , label: 
                Text(task.name ?? "no name")
            )
            .listRowBackground(Color(UIColor.systemBackground))
            
            
        .onDelete(perform: deleteItems)
        )
        
    
    private func deleteItems(offsets: IndexSet) 
        withAnimation 
            deleteAlert = Alert(title: Text("Sure you want to delete?"), primaryButton: Alert.Button.destructive(Text("yes"), action: 
                let objs = offsets.map  objects[$0] 
                
                for obj in objs
                    
                    vm.deleteObject(object: obj)
                
                //Because the objects in the sections aren't being directly observed
                vm.objectWillChange.send()
                
            ), secondaryButton: Alert.Button.cancel())
            
            
            self.presentAlert = true
            
        
    


struct TaskListView_Previews: PreviewProvider 
    static var previews: some View 
        NavigationView
            TaskListView()
        
    

【讨论】:

以上是关于无法在部分列表视图中编辑/删除正确的项目的主要内容,如果未能解决你的问题,请参考以下文章

无法在列表视图项目中正确选择文本

如何使用里面的按钮从列表视图中删除一个项目?

phpmyadmin:从多个表创建的视图现在 id 字段不是唯一的,无法编辑,更新删除

如何从 SwiftUI 的详细信息视图(编辑项目视图)中删除核心数据条目?

使用微调器从自定义列表视图中删除了错误的行

“如何删除 sqlite 数据库中正确的列表视图项