Home > Mobile >  SwiftUI: deleting Managed Object from cell view crashes app?
SwiftUI: deleting Managed Object from cell view crashes app?

Time:07-30

I have the following code, I just modified the sample SwiftUI project Xcode creates for you.

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
        animation: .default)
    private var items: FetchedResults<Item>

    var body: some View {
        NavigationView {
            List {
                ForEach(items) { item in
                    NavigationLink {
                        Text(item.timestamp!, formatter: itemFormatter)
                    } label: {
                        CellView(item: item)
                    }
                }
                .onDelete(perform: deleteItems)
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    EditButton()
                }
                ToolbarItem {
                    Button(action: addItem) {
                        Label("Add Item", systemImage: "plus")
                    }
                }
            }
            Text("Select an item")
        }
    }

    private func addItem() {
        withAnimation {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()

            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            offsets.map { items[$0] }.forEach { item in
                viewContext.delete(item)
            }

            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
}

struct CellView: View {
    
    @Environment(\.managedObjectContext) private var viewContext
    @ObservedObject var item:Item
    
    var body: some View {
        
        HStack {
            Text(item.timestamp!, formatter: itemFormatter) // <<- CRASH ON DELETE
            Button {
                withAnimation {
                    viewContext.delete(item)
                    try? viewContext.save()
                }
                
            } label: {
                Text("DELETE")
                    .foregroundColor(.red)
            }
            .buttonStyle(.borderless)
        }
        
    }
    
}

private let itemFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.timeStyle = .medium
    return formatter
}()

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

Item is defined like this:

extension Item {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Item> {
        return NSFetchRequest<Item>(entityName: "Item")
    }

    @NSManaged public var timestamp: Date?

}

extension Item : Identifiable {

}

When I delete an item either via swipe or by pressing the delete button the app crashes. If I do away with the CellView and put all the code directly in the navigation link... it does not crash.

How can I fix this while keeping the separate CellView?

What I've learned so far:

If I change:

@ObservedObject var item:Item

to:

@State var item:Item

The crash goes away. Any idea?

CodePudding user response:

Try to avoid force-unwrap optionals as much as possible and use conditions (view builder allows now such constructions)

HStack {
  if let timestamp = item.timestamp {
    Text(timestamp, formatter: itemFormatter)
  }
  • Related