Reputation: 2856
TL;DR version:
I am using .onDisappear method in SwiftUI in my app having Core Data. How to make this method conditional if a particular instance of my Entity exists? (Specifically, based on existence of the item details I am looking at, self.item)
--
MORE DETAILS:
Simple example of the problem
Here is a very simple app with a list of items. One Entity is: Item. It has 2 attributes: date (Date), name (String). Please create this Entity if you will use the code below.
There are 2 SwiftUI views: ContentView.swift
import SwiftUI
struct ContentView: View {
@Environment(\.managedObjectContext) var moc
var fetchRequest: FetchRequest<Item>
var items: FetchedResults<Item> { fetchRequest.wrappedValue }
var body: some View {
NavigationView {
List {
ForEach(items, id: \.self) {item in
NavigationLink(destination: DetailsView(item: item)) {
Text("\(item.name ?? "default item name")")
}
}
}
.navigationBarTitle("Items")
.navigationBarItems(
leading:
Button(action: {
for number in 1...3 {
let item = Item(context: self.moc)
item.date = Date()
item.name = "Item \(number)"
do {
try self.moc.save()
}catch{
print(error)
}
}
}) {
Text("Add 3 items")
}
)
}
}
init() {
fetchRequest = FetchRequest<Item>(entity: Item.entity(), sortDescriptors: [
NSSortDescriptor(keyPath: \Item.name, ascending: true)
])
}
}
DetailsView.swift
import SwiftUI
struct DetailsView: View {
@Environment(\.managedObjectContext) var moc
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var item: Item
var body: some View {
VStack {
Text("\(item.name ?? "default item name")")
}
.navigationBarItems(
trailing:
Button(action: {
self.moc.delete(self.item)
do {
try self.moc.save()
self.presentationMode.wrappedValue.dismiss()
}catch{
print(error)
}
}) {
Text("Delete")
.foregroundColor(.red)
}
)
//This is causing problems. It tries to save new item name, even if I just deleted this item.
.onDisappear {
self.item.name = "new name"
try? self.moc.save()
}
}
}
What this example app does is:
THE PROBLEM
When I delete an item from the DetailsView, I am getting an error:
020-06-26 19:02:24.534275+0700 list-delete[57017:18389643] [error] error: Mutating a managed object 0x80784bd2555f567e x-coredata://99AA316D-D816-49BB-9C09-943F307C3174/Item/p41 (0x600002f03d40) after it has been removed from its context. CoreData: error: Mutating a managed object 0x80784bd2555f567e x-coredata://99AA316D-D816-49BB-9C09-943F307C3174/Item/p41 (0x600002f03d40) after it has been removed from its context.
I have eventually figured out that the problem is with .onDisappear method. Which is triggered even after deleting the item. The main purpose of using .onDisappear here was to make it available to edit an item in DetailsView and .onDisappear would save the changes in CoreData. And this alone works great too.
How I fixed the bug
I have added a @State private var itemExists = true. When I tap Delete button, self.itemExists changes to false. And I have added an IF statement to .onDisappear using this itemExists variable. Updated code of DetailsView looks like this:
import SwiftUI
struct DetailsView: View {
@Environment(\.managedObjectContext) var moc
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var item: Item
@State private var itemExists = true
var body: some View {
VStack {
Text("\(item.name ?? "default item name")")
}
.navigationBarItems(
trailing:
Button(action: {
self.moc.delete(self.item)
self.itemExists = false
do {
try self.moc.save()
self.presentationMode.wrappedValue.dismiss()
}catch{
print(error)
}
}) {
Text("Delete")
.foregroundColor(.red)
}
)
.onDisappear {
if self.itemExists {
self.item.name = "new name"
try? self.moc.save()
}
}
}
}
Now the question is if this additional variable is necessary? Or is there a simpler way of checking if Core Data's Entity instance exists?
I have tried another IF:
if self.item != nil {
But this just gave me a warning:
Comparing non-optional value of type 'Item' to 'nil' always returns true
Upvotes: 0
Views: 316
Reputation: 119242
isDeleted
will be true on any managed object that has been marked for deletion. You can check that instead of tracking with a separate variable. You might want to only save the context on onDisappear
instead of after the deletion, though, as the documentation seems to suggest it's intended for use between deleting and saving.
Upvotes: 2