SwiftUI holding reference to deleted core data object causing crash

后端 未结 6 600
谎友^
谎友^ 2021-01-31 04:38

Im finding it impossible to use core data with SwiftUI, because as I pass a core data to a view observed object variable, the navigation link view will hold a reference to the o

相关标签:
6条回答
  • 2021-01-31 04:52

    I had the same issue recently. Adding an entity property to the view fixed it.

    ForEach(entities, id: \.self) { entity in
        Button(action: {
    
        }) {
            MyCell(entity: entity)
        }
    }
    

    To

    ForEach(entities, id: \.self) { entity in
        Button(action: {
    
        }) {
            MyCell(entity: entity, property: entity.property)
        }
    }
    

    I suspect that the nullable Core Data entity is the cause of the issue, where as adding a non-nil property as a var (e.g, var property: String) fixed it

    0 讨论(0)
  • 2021-01-31 04:53

    I have tried all previous solutions, none worked for me.

    This one, worked.

    I had my list like this:

    List {
      ForEach(filteredItems, id: \.self) { item in
        ListItem(item:item)
      }
    .onDelete(perform: deleteItems)
    
    
    private func deleteItems(offsets: IndexSet) {
      //deleting items
    

    This was crashing.

    I modified the code to this one

    List {
      ForEach(filteredItems, id: \.self) { item in
        ListItem(item:item)
      }
      .onDelete { offsets in
         // delete objects
      }
    

    This works fine without crashing.

    For heaven's sake, Apple!

    0 讨论(0)
  • 2021-01-31 04:56

    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.

    0 讨论(0)
  • 2021-01-31 05:04

    A view modifier for this (based on conditional view modifiers):

    import SwiftUI
    import CoreData
    
    extension View {
        @ViewBuilder
        func `if`<Transform: View>(
            _ condition: Bool,
            transform: (Self) -> Transform
        ) -> some View {
            if condition {
                transform(self)
            } else {
                self
            }
        }
    }
    
    extension View {
        func hidingFaults(_ object: NSManagedObject) -> some View {
            self.if(object.isFault) { _ in EmptyView() }
        }
    }
    

    Having said that, it's worth checking you're performing CoreData operations asynchronously on the main thread, doing it synchronously can be a source of grief (sometimes, but not always).

    0 讨论(0)
  • 2021-01-31 05:07

    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!

    0 讨论(0)
  • 2021-01-31 05:12

    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.

    0 讨论(0)
提交回复
热议问题