SwiftUI holding reference to deleted core data object causing crash

I have had the same issue for a while, the solution for me was pretty simple: In the View where the @ObservedObject is stored I simply put this !managedObject.isFault.

I experienced this class only with ManagedObjects with a date property, I don't know if this is the only circumstance the crash verifies.

import SwiftUI

struct Cell: View {
    
    @ObservedObject var managedObject: MyNSManagedObject
    
    var body: some View {
        if !managedObject.isFault {
           Text("\(managedObject.formattedDate)")
        } else {
            ProgressView()
        }
    }
}


After some research online, it's clear to me that this crash can be caused by many things related to optionals. For me, I realized that declaring a non-optional Core Data attribute as an optional in the NSManagedObject subclass was causing the issue.

Specifically, I have a UUID attribute id in Core Data that cannot have a default value, but is not optional. In my subclass, I declared @NSManaged public var id: UUID. Changing this to @NSManaged public var id: UUID? fixed the problem immediately.


I basically had the same issue. It seems that SwiftUI loads every view immediately, so the view has been loaded with the Properties of the existing CoreData Object. If you delete it within the View where some data is accessed via @ObservedObject, it will crash.

My Workaround:

  1. The Delete Action - postponed, but ended via Notification Center
    Button(action: {
      //Send Message that the Item  should be deleted
       NotificationCenter.default.post(name: .didSelectDeleteDItem, object: nil)

       //Navigate to a view where the CoreDate Object isn't made available via a property wrapper
        self.presentationMode.wrappedValue.dismiss()
      })
      {Text("Delete Item")}

You need to define a Notification.name, like:

extension Notification.Name {

    static var didSelectDeleteItem: Notification.Name {
        return Notification.Name("Delete Item")
    }
}
  1. On the appropriate View, lookout for the Delete Message

// Receive Message that the Disease should be deleted
    .onReceive(NotificationCenter.default.publisher(for: .didSelectDeleteDisease)) {_ in

        //1: Dismiss the View (IF It also contains Data from the Item!!)
        self.presentationMode.wrappedValue.dismiss()

        //2: Start deleting Disease - AFTER view has been dismissed
        DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(1)) {self.dataStorage.deleteDisease(id: self.diseaseDetail.id)}
    }

  1. Be safe on your Views where some CoreData elements are accessed - Check for isFault!

    VStack{
         //Important: Only display text if the disease item is available!!!!
           if !diseaseDetail.isFault {
                  Text (self.diseaseDetail.text)
            } else { EmptyView() }
    }

A little bit hacky, but this works for me.


I encountered the same issue and did not really find a solution to the root problem. But now I "protect" the view that uses the referenced data like this:

var body: some View {
    if (clip.isFault) {
        return AnyView(EmptyView())
    } else {
        return AnyView(actualClipView)
    }
}

var actualClipView: some View {
    // …the actual view code accessing various fields in clip
}

That also feelds hacky, but works fine for now. It's less complex than using a notification to "defer" deletion, but still thanks to sTOOs answer for the hint with .isFault!

Tags:

Ios

Swift

Swiftui