Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

macOS SwiftUI: how to trigger deleting an item?

I am working on my very first SwiftUI app on macOS Monterey 12.6 using Xcode 14.2. I have created a new Multiplatform > App project (enabling Core Data) and Xcode generated a working sample app. The view part looks like this:

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

Adding new items by clicking the + button works - but how do I delete them ?

I suppose the sample code is mostly generated with iOS in mind; I tried running the app on an iPhone simulator: Deleting there just happens as usual by swiping the item in the list.

However, on macOS it seems I need some other means to trigger the deletion.

I have added a ToolbarItem / Button analogous to Add Item and Xcode also generated this deleteItems function:

private func deleteItems(offsets: IndexSet) {
    withAnimation {
        offsets.map { items[$0] }.forEach(viewContext.delete)
        do {
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
    }
}

I figure on iOS, this function gets called somehow automagically via the onDelete modifier (?).

I imagine I could try to get the index of the item in the list, turn that into an IndexSet somehow and connect calling deleteItems(...) to the delete button, but I have a gut feeling SwiftUI provides some much simpler way for such a standard task - after all, that's CRUD on the Core Data model...

I tried to get sample code from here, here, here and here to work, but I clearly seem to lack the SwiftUI experience required to understand the code and adapt it for my needs.

How do I trigger deleting an item ?

like image 763
ssc Avatar asked Oct 31 '25 07:10

ssc


1 Answers

If this List were not in a NavigationView, you would have been able to swipe left with 2 fingers on a row, and see the delete button. Clicking that will trigger the onDelete modifier.

However, since the list is in a NavigationView, and macOS displays it and the navigation destination in a "split" way, similar to a NavigationSplitView. In this mode of presentation, there is no swipe with 2 fingers gesture.

Also note that NavigationView is deprecated in the newest version of macOS. You should use NavigationSplitView if you want a "split" presentation.

If you use a NavigationStack, which only shows the navigation links, and only shows the destination if you click on one of them, then the swipe left with 2 fingers gesture is supported.

Otherwise, you will have to come up with your own UI of deleting things. You can take inspiration from how existing macOS apps do it. For example, in the Notes app, the navigation bar has a "trash" button.

enter image description here

You can consider putting this into your toolbar:

Button {
    withAnimation {
        selection.forEach(viewContext.delete)
        do {
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
    }
} label: {
    Image(systemName: "trash")
}

where selection is set up like this:

@State var selection = Set<Item>()
// ...
List(selection: $selection) {
    // ...
}

A more complete example:

@State var items = ["1", "2", "3"]
@State var selection = Set<String>()

var body: some View {
    NavigationView {
        List(selection: $selection) {
            ForEach(items, id: \.self) { (item) in
                NavigationLink {
                    Text("Item at \(item)")
                } label: {
                    Text(item)
                }
            }
        }
        .toolbar {
            ToolbarItem {
                Button {
                    withAnimation {
                        items.removeAll(where: { selection.contains($0) })
                    }
                } label: {
                    Image(systemName: "trash")
                }
            }
        }
    }.onChange(of: selection) { newValue in
        print(newValue)
    }
}

An alternative is to add a contextMenu item for deleting items, similar to how the Contacts app does it.

like image 171
Sweeper Avatar answered Nov 01 '25 20:11

Sweeper



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!