0

On an iPad, the leftmost pane (Sidebar) of a SplitNavigationView has a toolbar button that displays a sheet. This sheet can load or delete all SwiftData objects. These objects are listed in the second pane (Content). Hitting the load button creates some new sample data and displays it no problem. However hitting the delete button appears to have no effect and doesn't trigger a view update.

The model context is in the environment and the list is fed with an @Query. I've tried injecting the ModelContext into the SettingsSheet rather than accessing it through the environment but the result is the same. In fact on a relaunch the data is sometimes not even deleted.

I've cut the code down to the bare minimum to show the issue and it should just be a copy, paste and run. Am I misunderstanding how the SwiftData operations percolate through the environment? Does the presence of the sheet need handling differently? Any help gratefully received.

import SwiftData
import SwiftUI

@main
struct ProblemTestApp: App {

    let container: ModelContainer

    var body: some Scene {
        WindowGroup {
            NavigationSplitView {
                SidebarView()
            } content: {
                ContentView()
            } detail: {
                DetailTabbedView()
            }
            .modelContainer(container)
        }
    }

    init() {
        let schema = Schema( [ Monkey.self ] )
        let configuration = ModelConfiguration("ProblemTestApp", schema: schema)
        do {
            container = try ModelContainer(for: schema, configurations: configuration)
        } catch {
            fatalError("Could not configure the SwiftData container.")
        }
    }
}

// SidebarView

struct SidebarView: View {

    @State private var showingSettingsSheet = false

    var body: some View {
        Text("Sidebar Monkey")
            .toolbar {
                Button {
                    showingSettingsSheet.toggle()
                } label: {
                    Label("Show settings", systemImage: "gearshape")
                }
            }
        .sheet(isPresented: $showingSettingsSheet) { /* On dismiss. */ } content: {
            SettingsSheet()
        }
    }
}

// ContentView

struct ContentView: View {

    @Query var allMonkeys: [Monkey]

    var body: some View {
        List {
            Text("Monkey count = \(allMonkeys.count)")
            ForEach(allMonkeys) { monkey in
                Text(monkey.name)
            }
        }
    }
}

// DetailTabbedView

struct DetailTabbedView: View {

    var body: some View {
        Text("Detail Tabbed View (tabs to come)")
    }
}

// Monkey model

@Model
final class Monkey: Identifiable {

    var id: UUID = UUID()
    var name: String = ""

    init(name: String) {
        self.name = name
    }
}

// SettingsSheet

struct SettingsSheet: View {

    @Environment(\.modelContext) var context

    var body: some View {
        NavigationStack {
            HStack {
                Button("Load") {
                    for _ in 0...9 {
                        let monkey = Monkey(name: String(Int.random(in: 0...999)))
                        context.insert(monkey)
                    }
                }
                Button("Delete") {
                    do {
                        try context.delete(model: Monkey.self)
                        print("Deleted all the Monkeys")
                    } catch {
                        print("Failed to delete all Monkeys.")
                    }
                }
            }
            .navigationTitle("Monkey Settings")
        }
    }
}
2
  • Observation: why do you have a NavigationStack in the sheet, you are not navigating to any destinations. Have you tried adding save() after the delete. Commented Jun 25, 2024 at 23:56
  • @workingdog support Ukraine - The NavigationStack is there just for the title and a toolbar in the real app. Is there a better way to achieve that? I have tried the saving the context after both the load and delete actions but no luck. Commented Jun 26, 2024 at 5:51

2 Answers 2

1

The problem is with the function you are using to delete.

When I run your code in the debugger I notice a few things of interest:

Directly after the call to context.delete(model: Monkey.self) I can see in the debugger that the model context holds no deleted objects since the array property deletedModelsArray is empty.

Furthermore if I add a fetch before the delete and then after the delete call examine the isDeleted flag of an object then it is false.

So clearly this method is some kind of batch method that does not update the state of the ModelContext or any in-memory model objects and I assume this is for performance reasons which makes sense. This could have been better documented though.

For UI near operations I recommend you resort to individual deletes instead because then the ModelContext object and your view will be properly updated.

let monkeys = try context.fetch(FetchDescriptor<Monkey>())
for monkey in monkeys {
    context.delete(monkey)                
}
                    

or more compact if you prefer

try context.fetch(FetchDescriptor<Monkey>()).forEach(context.delete)
Sign up to request clarification or add additional context in comments.

Comments

-1
try context.delete(model: Monkey.self)

This code deletes what? You are saying to delete a Monkey object, but not a specific one.

Check this out:

How to let users delete rows from a list

4 Comments

Hi thanks for the comment but that line of code deletes all objects of the Monkey type. Saves time and code when you need to delete them all at once.
You are correct. I didn’t read the part that says that it will delete all. I thought you were trying to delete only one. My bad!
Try putting your modelcontainer on the window group and not the splitview. Also you don’t need to mark your model as identifiable, a model already is. You don’t need the id property.
Good point concerning the model, thanks. Moving the modelContainer still doesn't trigger a view update but it has caused a relaunch (following a delete) to consistently remove the objects which wasn't happening before. Small steps, so thanks again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.