Fatal crash when using CoreData and deleting item from list with Section headers

Greetings - The following code appears to work, but when a list item is deleted from a Category section that contains other list items, the app crashes (error = "Thread 1: EXC_BREAKPOINT (code=1, subcode=0x180965354)"). I've confirmed that the intended item is deleted from the appData.items array - the crash appears to happen right after the item is deleted.

I suspect that the problem somehow involves the fact that the AppData groupedByCategory dictionary and sortedByCategory array are computed properties and perhaps not updating as intended when an item is deleted? Or maybe the ContentView doesn't know they've been updated? My attempt to solve this by adding "appData.objectWillChange.send()" has not been successful, nor has my online search for solutions to this problem.

I'm hoping someone here will either know what's happening or know I could look for additional solutions to try. My apologies for all of the code - I wanted to include the three files most likely to be the source of the problem. Length restrictions prevent me from including the "AddNewView" code and some other details, but just say the word if that detail would be helpful.

Many, many thanks for any help anyone can provide!

@main
struct DeletionCrashApp: App {
    let persistenceController = PersistenceController.shared
    
    // Not sure where I should perform this command
    @StateObject var appData = AppData(viewContext: PersistenceController.shared.container.viewContext)
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
                .environmentObject(appData)
        }
    }
}


import Foundation
import SwiftUI
import CoreData

class AppData: NSObject, ObservableObject {
    
    // MARK: - Properties
    @Published var items: [Item] = []
    private var fetchedResultsController: NSFetchedResultsController<Item>
    private (set) var viewContext: NSManagedObjectContext // viewContext can be read but not set from outside this class
    
    // Create a dictionary based upon the category
    var groupedByCategory: [String: [Item]] {
        Dictionary(grouping: items.sorted(), by: {$0.category})
    }
    
    // Sort the category-based dictionary alphabetically
    var sortedByCategoryHeaders: [String] {
        groupedByCategory.map({ $0.key }).sorted(by: {$0 < $1})
    }
    
    // MARK: - Methods
    func deleteItem(itemObjectID: NSManagedObjectID) {
       do {
            guard let itemToDelete = try viewContext.existingObject(with: itemObjectID) as? Item else {
                return // exit the code without continuing or throwing an error
            }
            viewContext.delete(itemToDelete)
        } catch {
            print("Problem in the first do-catch code: \(error)")
        }
        
        do {
            try viewContext.save()
        } catch {
            print("Failure to save context: \(error)")
        }
        
    }
    
    // MARK: - Life Cycle
    init(viewContext: NSManagedObjectContext) {
        
        self.viewContext = viewContext

        let request = NSFetchRequest<Item>(entityName: "ItemEntity")
        request.sortDescriptors = [NSSortDescriptor(keyPath: \Item.name, ascending: true)]
        
        fetchedResultsController = NSFetchedResultsController(fetchRequest: request,
                                                              managedObjectContext: viewContext,
                                                              sectionNameKeyPath: nil,
                                                              cacheName: nil)
        super.init()
        
        fetchedResultsController.delegate = self
        
        do {
            try fetchedResultsController.performFetch()
            guard let items = fetchedResultsController.fetchedObjects else {
                return
            }
            self.items = items
        } catch {
            print("failed to fetch items: \(error)")
        }
        
    } // end of init()
    
} // End of AppData

extension AppData: NSFetchedResultsControllerDelegate {
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        guard let items = controller.fetchedObjects as? [Item] else { return }
        self.items = items
    }
}


import SwiftUI
import CoreData

struct ContentView: View {
    
    @Environment(\.managedObjectContext) private var viewContext
    @EnvironmentObject var appData: AppData
    
    @State private var showingAddNewView = false
    @State private var itemToDelete: Item?
    @State private var itemToDeleteObjectID: NSManagedObjectID?

    var body: some View {
        NavigationView {
            List {
                ForEach(appData.sortedByCategoryHeaders, id: \.self) { categoryHeader in
                    Section(header: Text(categoryHeader)) {
                        ForEach(appData.groupedByCategory[categoryHeader] ?? []) { item in
                                Text(item.name)
                            .swipeActions(allowsFullSwipe: false) {
                                Button(role: .destructive) {
                                    self.itemToDelete = appData.items.first(where: {$0.id == item.id})
                                    self.itemToDeleteObjectID = itemToDelete!.objectID
                                    appData.deleteItem(itemObjectID: itemToDeleteObjectID!)
                                 // appData.objectWillChange.send() <- does NOT fix the fatal crash
                                } label: {
                                    Label("Delete", systemImage: "trash.fill")
                                }
                            } // End of .swipeActions()
                        } // End of ForEach(appData.groupedByReplacementCategory[categoryHeader]
                    } // End of Section(header: Text(categoryHeader)
                } // End of ForEach(appData.sortedByCategoryHeaders, id: \.self)
            } // End of List
            .navigationBarTitle("", displayMode: .inline)
            .navigationBarItems(
                trailing:
                    Button(action: {
                        self.showingAddNewView = true
                    }) {
                        Image(systemName: "plus")
                    }
            )
            .sheet(isPresented: $showingAddNewView) {
                // show AddNewView here
                AddNewView(name: "")
            }
        } // End of NavigationView
    } // End of body
} // End of ContentView

extension Item {
    @nonobjc public class func fetchRequest() -> NSFetchRequest<Item> {
        return NSFetchRequest<Item>(entityName: "ItemEntity")
    }
    @NSManaged public var category: String
    @NSManaged public var id: UUID
    @NSManaged public var name: String
}
extension Item : Identifiable {
}

Update to my question: I attempted to convert the computed properties to @Published properties with the following edits to my AppData code (and by calling the updateSortedList() function in my ContentView and AddNewView code. These edits did NOT solve the fatal crash or change the error code. I'm still very much stuck on this, and really welcome input. Here are my edits:

    @Published var groupedByCategory: Dictionary<String, [Item]> = [:]
    @Published var sortedByCategoryHeaders: [String] = []
    
    func updateSortedList(items: [Item]) {
        self.groupedByCategory = Dictionary(grouping: items.sorted(), by: {$0.category})
        self.sortedByCategoryHeaders = groupedByCategory.map({ $0.key }).sorted(by: {$0 < $1})
    }

init(viewContext: NSManagedObjectContext) {
        
        self.viewContext = viewContext
        let request = NSFetchRequest<Item>(entityName: "ItemEntity")
        request.sortDescriptors = [NSSortDescriptor(keyPath: \Item.name, ascending: true)]
        
        fetchedResultsController = NSFetchedResultsController(fetchRequest: request,
                                                              managedObjectContext: viewContext,
                                                              sectionNameKeyPath: nil,
                                                              cacheName: nil)
        super.init()
        
        fetchedResultsController.delegate = self
        
        do {
            try fetchedResultsController.performFetch()
            guard let items = fetchedResultsController.fetchedObjects else {
                return
            }
            self.items = items
        } catch {
            print("failed to fetch items: \(error)")
        }
        
        self.groupedByCategory = Dictionary(grouping: items.sorted(), by: {$0.category})
        self.sortedByCategoryHeaders = groupedByCategory.map({ $0.key }).sorted(by: {$0 < $1})
        
        
    } // end of init()

Another update to this question: I recreated this toy app WITHOUT using CoreData, instead archiving to the FileManager .documentDirectory. The app did NOT exhibit the fatal crash, even though all other code was the same (e.g., the category headers were still computed properties, etc.). I then returned to the original, posted, CoreData version of the app and removed: do { try viewContext.save() } catch { print("Failure to save context: \(error)") } from the deleteItem() function. This eliminated the fatal crash! Obviously it's not a viable solution - users need to be able to edit the list - but it tells me exactly where the problem is. I still don't understand why, though, or how to solve it. Any guidance is most welcome.

Hi! I was stuck on this too, but when I saw your comment, I had an idea. This is kind of a hacky solution, but basically I moved do { try viewContext.save() } catch { print("Failure to save context: \(error)") } to an .onDisappear function. That way the app would save the deletion only after the view is out of sight (because I'm assuming the problem lies in the ForEach statement not knowing how to update the view when its data suddenly becomes empty). I also added a condition so that it only tries to save the view context if something has been deleted. I'm a little late to the party, but I hope this helps you and/or anyone else who comes across this issue!

Fatal crash when using CoreData and deleting item from list with Section headers
 
 
Q