swiftswiftuiswiftdataswiftui-navigationsplitview

onDelete Does not Reliably Delete List Items in SwiftData NavigationSplitView


I'm building a new version of an app with SwiftData and am having issues with list deletions in iPad landscape mode. When deleting items, the deleted item randomly reappears as though not deleted from the context. I have included code for a very simple example to illustrate the issue. If you randomly select a row, then delete another row by swipe to delete and continue testing other sequences you will find that the deleted rows often disappear only to reappear when another row is selected.

I realize, of course, this is Beta software, but this seems pretty basic so I'm wondering if I have missed something in the SwiftData setup or the .onDelete code that I have used it in the past. The behavior is the same for Beta 4 and Beta 5.

struct ContentView: View {

    @Environment(\.modelContext) private var context
    @Query(sort: \Thing.name, order: .forward) var things: [Thing]

    @State private var selectedThing: Thing?

    var body: some View {
    
        NavigationSplitView {
            VStack {
                Text("Some Things")
                List(selection: $selectedThing) {
                    ForEach(things, id: \.self) { thing in
                            HStack {
                                Text(thing.name)
                                Text(thing.areYouSure.description)
                            }
                    }//for each
                    .onDelete{ indexSet in
                        indexSet.forEach { index in
                            if things[index] == selectedThing {
                                context.delete(things[index])
                            
                                //this does not make any difference
                                do {
                                    try context.save()
                                } catch {
                                    print(error)
                                }
                                //this does not make any difference
                                //selectedThing = nil
                            }
                        }
                    }//on delete
                }//list
            }//v
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    Button {
                        AddFourThings()
                    } label: {
                        Image(systemName: "plus")
                    }
                }
            }
        } detail: {//nav split sidebar
            ZStack {
                if let selectedThing {
                    DetailView(thing: selectedThing)
                } else {
                    Text("Choose a Thing")
                }
            }
        }//nav split detail
    
    }//body

    private func AddFourThings() {
    
        let thing1 = Thing(name: "Thing One", areYouSure: true)
        let thing2 = Thing(name: "Thing Two", areYouSure: false)
        let thing3 = Thing(name: "Thing Three", areYouSure: true)
        let thing4 = Thing(name: "Thing Four", areYouSure: false)
    
        context.insert(thing1)
        context.insert(thing2)
        context.insert(thing3)
        context.insert(thing4)
    
    }//add four

}//struct

struct DetailView: View {

    @State private var isOn: Bool = false

    var thing: Thing

    var body: some View {
        VStack {
            Text(thing.name)
                .font(.title)
            Text(thing.areYouSure.description)
                .font(.title)
            Text(isOn.description)
                .font(.title)
            Toggle("Toggle Me", isOn: $isOn)
                .font(.title)
        }
    }

}//detail view

@Model
class Thing: Identifiable {

    var name: String
    var areYouSure: Bool

    init(name: String, areYouSure: Bool) {
        self.name = name
        self.areYouSure = areYouSure
    }
}//class thing

Any guidance would be appreciated. Xcode 15.0 beta 4 (15A5195m), iOS 17


Solution

  • For others. Apparently this is a known issue at Apple and is in the Release Notes (109838173). The suggested workaround is to explicitly save after the delete, however that still does not work for me. In my situation, if I remove the .onDelete modifier and instead add a swipe action inside the ForEach loop, that works for me and is acceptable. Hopefully the issue will be resolved before the Beta products are released.

    ForEach(things, id: \.self) { thing in
        HStack {
            Text(thing.name)
            Text(thing.areYouSure.description)
        }
        .swipeActions(edge: .trailing) {
            Button(role: .destructive) {
                context.delete(thing)
                selectedThing = nil
                do {
                    try context.save()
                } catch {
                    print(error.localizedDescription)
                }
            } label: {
                Label("Delete", systemImage: "trash")
            }
        }
    }//for each
    

    Good Luck.