I have a CoreData entity called TestItem:
If there's 1 entity in collection, deleting this entity will crash the app with an error: "Thread 1: EXC_BREAKPOINT (code=1, subcode=0x197c0b8b4)". As it seems, nothing will prevent DetailView from crashing once it's been initialized with ObservedObject, which cannot be nil. I can't find a way to deinit DetailView before deleting the object. To have more information why I couldn't deinit DetailView, see my previous post
Here is a code example reproducing the error as following:
import SwiftUI
import CoreData
struct ContentView: View {
let persistence = PersistenceController.shared
@FetchRequest(entity: TestItem.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \TestItem.date, ascending: false)])
var items: FetchedResults<TestItem>
@State var selectedItem: TestItem?
@State var selectedItemIndex: Int?
var body: some View {
NavigationSplitView {
List(selection: $selectedItem) {
ForEach(items) { item in
VStack(alignment: .leading) {
Text(item.name)
.foregroundColor(selectedItem == item ? .blue : .white)
}
.onTapGesture {
selectedItem = item
selectedItemIndex = items.firstIndex(of: item)
}
}
}
.toolbar {
Button(action: {
addItem()
}) {
Image(systemName: "plus.square")
}
}
} detail: {
if let selectedItem = selectedItem {
DetailView(item: selectedItem, onDelete: {
guard !items.isEmpty else {
self.selectedItem = nil
selectedItemIndex = nil
return
}
if selectedItemIndex! > items.indices.last! {
selectedItemIndex = selectedItemIndex! - 1
}
self.selectedItem = items[selectedItemIndex!]
})
}
}
}
private func addItem() {
let newItem = TestItem(context: persistence.container.viewContext)
newItem.name = "Test Item"
newItem.date = Date()
do {
try persistence.container.viewContext.save()
selectedItem = newItem
selectedItemIndex = 0
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
struct DetailView: View {
@ObservedObject var item: TestItem
var onDelete: () -> Void
var body: some View {
VStack {
Text(item.name)
DatePicker("Date", selection: $item.date, displayedComponents: [.date])
HStack {
Button(action: {
deleteItem(item)
}) {
Image(systemName: "trash")
}
Spacer()
}
Spacer()
}
}
private func deleteItem(_ item: TestItem) {
item.managedObjectContext?.delete(item)
DispatchQueue.main.async {
do {
try item.managedObjectContext?.save()
onDelete()
} catch let error as NSError {
print("Error deleting data: \(error.localizedDescription)")
}
}
}
}
It turns out that the crashed is caused by the DatePicker
that I guess holds a strong reference to the Item
object. If we break this reference the app will not crash when the last object is deleted.
This can be done by using a State
object instead for the DatePicker
selection
@State private var selectedDate: Date = .now
used here
DatePicker("Date", selection: $selectedDate, displayedComponents: [.date])
and update the item
when this property is changed
.onChange(of: selectedDate) { date in
item.date = date
}
We also need to update selectedDate
when a new TestItem
object is selected in the sidebar
.onChange(of: item) { newItem in
selectedDate = newItem.date
}
I don't know if this relevant for any other components than the DatePicker
, I for instance changed so that I could edit the name attribute using a TextField
but that didn't cause any issues so it's definitely not an issue for all components where you can mutate the content.