Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Crash when delete a record from coredata in SwiftUI

I make a list for audio items from coredata. after deleting, crash reported as "EXC_BREAKPOINT (code=1, subcode=0x1b8fb693c)", why?

When using

ForEach(items, id: \.self)

, it works. But My Audio has id property and follow Identifiable protocol.

UPDATE: I found adding a if{} clause will fix crash, but why? Breakpoint at "static UUID.unconditionallyBridgeFromObjectiveC(:) ()".

struct Test1View: View {
    @Environment(\.managedObjectContext) var context
    @FetchRequest(fetchRequest: Audio.fetchAllAudios()) var items: FetchedResults<Audio>
    var body: some View {
        List {
            ForEach(items) { item in
                if true { // <- this if clause fix crash, but why?
                    HStack {
                        Text("\(item.name)")
                    }
                }
            }.onDelete(perform: { indexSet in
                let index = indexSet.first!
                let item = self.items[index]
                self.context.delete(item)
                try? self.context.save()
            })
        }
    }
}

code as following:

class Audio: NSManagedObject, Identifiable {
    @NSManaged public var id: UUID
    @NSManaged public var name: String
    @NSManaged public var createAt: Date
}

struct Test1View: View {
    @Environment(\.managedObjectContext) var context
    var fetchRequest: FetchRequest<Audio> = FetchRequest<Audio>(entity: Audio.entity(), sortDescriptors: [NSSortDescriptor(key: "createAt", ascending: false)])
    var items: FetchedResults<Audio> { fetchRequest.wrappedValue }
    var body: some View {
        List {
            ForEach(items) { item in
                HStack {
                    Text("\(item.name)")
                }
            }.onDelete(perform: { indexSet in
                let index = indexSet.first!
                let item = self.items[index]
                self.context.delete(item)
                try? self.context.save()
            })
        }
    }
}
like image 336
foolbear Avatar asked Mar 09 '20 16:03

foolbear


3 Answers

I had the same problem as you.

Probably it is a bad idea to use your own id property to make it Identifiable because Core Data is setting all the properties to nil when the object is deleted, even if you declared it differently in your Swift CoreData class. When deleting your entity, the id property gets invalidated and the objects .isFault property is set to true, but the SwiftUI ForEach still holds some reference to this ID object (=your UUID) to be able to calculate the "before" and "after" state of the list and somehow tries to access it, leading to the crash.

Therefore the following recommendations:

  1. Protect the detail view (in the ForEach loop by checking isFault:
if entity.isFault {
    EmptyView()
}
else {
    // your regular view body
}
  1. Expect your id property to be nil, either by defining it accordingly in your core data model as optional
@NSManaged public var id: UUID?

or by not relying on the Identifiable protocol in the SwiftUI ForEach loop:

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

or

ForEach(entities, id: \.objectID) { entity in ... }

Conclusion: you really do not need to make all your CoreData properties Swift Optionals. It's simply important that your id property referenced in the ForEach loop handles the deletion (=setting its value to nil) gracefully.

like image 66
pd95 Avatar answered Sep 19 '22 22:09

pd95


I found the reason of crash, must provide optional, because of OC/swift object conversion:

convert

class Audio: NSManagedObject, Identifiable {
    @NSManaged public var id: UUID
    @NSManaged public var name: String
    @NSManaged public var createAt: Date
}

to

class Audio: NSManagedObject, Identifiable {
    @NSManaged public var id: UUID?
    @NSManaged public var name: String?
    @NSManaged public var createAt: Date?
}
like image 35
foolbear Avatar answered Sep 19 '22 22:09

foolbear


I had the same issue over the weekend. It looks like SwiftUI want's to unwrap the value i read from CoreData and as the value is already deleted it crashes.

In my case i did solve it with nil coalescing on all values i use from CoreData.

You can try to provide a default value on your item.name with

ForEach(items) { item in
            HStack {
                Text("\(item.name ?? "")")
            }
        }
like image 31
Volker88 Avatar answered Sep 19 '22 22:09

Volker88