ios – SwiftUI and Core Data

0
75


I’m experimenting with Core Data and SwiftUI. I’m able to create and update an entry but when I try to delete, something strange happens. The model is a simple Entity with a “name”property.

image

I’m using the new NavigationSplitView as Apple suggest https://developer.apple.com/documentation/swiftui/migrating-to-new-navigation-types with sidebar and detail.

import SwiftUI

struct ContactsListView: View {
    @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Person.name, ascending: true)], animation: .default)
    private var contacts: FetchedResults<Person>
    
    @State private var isAddContactPresented = false
    @State private var isEditContactPresented = false
    @State private var selection: Person?
    
    var body: some View {
        NavigationSplitView {
            List(contacts, selection: $selection) { contact in
                NavigationLink(value: contact) {
                    Text(contact.name ?? "No name")
                }
            }
            .navigationTitle("Contacts")
            .toolbar {
                ToolbarItem(placement: .primaryAction) {
                    Button {
                        isAddContactPresented.toggle()
                    } label: {
                        Image(systemName: "plus")
                    }

                }
            }
            .sheet(isPresented: $isAddContactPresented) {
                AddContactView()
            }
        } detail: {
            if let contact = selection {
                VStack {
                    Text(contact.name ?? "No name")
                    
                    Button {
                        isEditContactPresented.toggle()
                    } label: {
                        Text("Edit")
                    }
                    .sheet(isPresented: $isEditContactPresented) {
                        AddContactView(contact: contact)
                    }

                }
            } else {
                Text("Create a new contact")
            }
        }

    }
}

In AddContactView I’m detecting if is a new contact or an existing contact.

struct AddContactView: View {
    @Environment(\.managedObjectContext) var viewContext
    @EnvironmentObject var dataController: DataController
    
    @Environment(\.dismiss) var dismiss
    
    @State private var name: String = ""
    
    let contact: Person?
    
    init(contact: Person? = nil) {
        self.contact = contact
        
        if let existingContact = contact {
            _name = State(wrappedValue: existingContact.name ?? "")
        }
    }
    
    var body: some View {
        NavigationStack {
            Form {
                Section {
                    TextField("name", text: $name)
                    
                    Button("Save") {
                        saveContact()
                    }
                }
                }
            .navigationTitle("Add/Edit View")
            .toolbar {
                if (contact != nil) {
                    ToolbarItem(placement: .primaryAction) {
                        Button {
                            if let contact = contact {
                               try? dataController.delete(contact)
                                dismiss()
                            }
                        } label: {
                            Image(systemName: "trash")
                                .foregroundColor(.red)
                        }

                    }
                }
            }
        }
    }
    
    func saveContact() {
        if let existingContact = self.contact {
            existingContact.name = name
        } else {
            let contact = Person(context: viewContext)
            contact.name = name
        }
        
        try? viewContext.save()
        dismiss()
    }
}

The problem is when I tap on the delete button in AddContactView, the contact is successfully deleted but the detailView is not dismissed and name it’s replaced by the nil coalescing string. How can I dismiss the View on iPhone and clear the detail on iPad?

Here’s a sample video

https://youtu.be/Bak7UR9kxKs

Edit: Added DataController code

import CoreData

class DataController: ObservableObject {
    static let shared = DataController()
    
    let container: NSPersistentCloudKitContainer
    
    init(inMemory: Bool = false) {
        container = NSPersistentCloudKitContainer(name: "DataModel")
        
        if inMemory {
            container.persistentStoreDescriptions.first!.url =  URL(fileURLWithPath: "/dev/null")
        }
        
        container.viewContext.automaticallyMergesChangesFromParent = true
        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        
        container.loadPersistentStores { storeDescription, error in
            if let error = error {
                fatalError("Fatal error loading store: \(error.localizedDescription)")
            }
        }
    }
    
    func save() throws {
        if container.viewContext.hasChanges {
            try? container.viewContext.save()
        }
    }
    
    func delete(_ object: NSManagedObject) throws {
        container.viewContext.delete(object)
        try save()
    }
    
}