mallow
mallow

Reputation: 2856

onDisappear if Core Data instance exists

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

Answers (1)

jrturton
jrturton

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

Related Questions