SwiftUI use editMode's onDelete modifier alongside with custom Swipe Actions on iOS 15

2.7k Views Asked by At

Initial description

I currently have a dynamic list and I'm using the SwiftUI built-in editMode feature and the built-in EditButton() in order to let the user to delete items from the list.

For the deletion part, I am using the onDelete modifier which adds the trailing red Delete swipe gesture as you now.

This is an example:

List {
    ForEach(someList) { someElement in
        Text(someElement.name)
    }
    .onDelete { (indexSet) in }
    .onMove { (source, destination) in }
}
.toolbar {
    ToolbarItem(placement: .navigationBarLeading) {
        EditButton()
    }
}
.environment(\.editMode, $editMode)

The goal

Now I also want to use the iOS 15 .swipeActions modifier that allows adding our own custom leading or trailing swipe gestures.

I want the user to be also able to edit the list element by swiping right, so I added a leading swipe action to my row:

Text(someElement.name)
.swipeActions(edge: .leading) {
    Button("Edit") { }
}

The button action would obviously contain a code allowing the edition.

The issue

Using the .swipeActions modifier breaks the normal behavior of the editMode and particularly the .onDelete modifier. Indeed, with this setup I cannot swipe left anymore in order to delete the row.

So here's the question: how can I use the SwiftUI editMode's .onDelete modifier on a list alongside with my own custom leading swipe action ?

Minimal reproducible example

Xcode playgrounds, for iOS 15.

import SwiftUI
import PlaygroundSupport

struct SomeList: Identifiable {
    let id: UUID = UUID()
    var name: String
}

let someList: [SomeList] = [
    SomeList(name: "row1"),
    SomeList(name: "row2"),
    SomeList(name: "row3")
]

struct ContentView: View {
    @State private var editMode = EditMode.inactive
    
    var body: some View {
        NavigationView {
            List {
                ForEach(someList) { someElement in
                    Text(someElement.name)
                    .swipeActions(edge: .leading) {
                        Button("Edit") { }
                    }
                }
                .onDelete { (indexSet) in }
                .onMove { (source, destination) in }
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    EditButton()
                }
            }
            .environment(\.editMode, $editMode)
        }
    }
}

PlaygroundPage.current.setLiveView(ContentView())

You can test the following: if you comment or remove this part:

.swipeActions(edge: .leading) {
    Button("Edit") { }
}

Then the .onDelete modifier works again. Putting it back on breaks the .onDelete feature.

Additional information

I am building this app for iOS 15 since I'm using the .swipeActions modifier.

I am willing to accept any solution that would allow achieving my goal, which is letting the user to swipe left to delete the row and swipe right to trigger any edition code. Even if that means using another way that the .swipeActions modifier, or another way that the .onDelete modifier of SwiftUI editMode, or both.

I had to build the app for iOS 15 only because of this .swipeActions modifier, so if I can build the app for lower iOS versions it is even better.

Let me now if you need any additional information. Thank you.

1

There are 1 best solutions below

0
Legolas Wang On

This is the one you want, to delete a specific row. You can use it inside the swipeAction(), as well as .onDelete()

It allows you to keep both of them functional.

func deleteSelectedItem(selectedItem: FetchedResults<Item>.Element) {
    viewContext.delete(selectedItem as NSManagedObject)
    saveCoreData()
}

Just use it like

ForEach(items) { item in
Text("").swipeActions() { Button { deleteSelectedItem(selectedItem: item) } }

And it will delete the item which the swipe was triggered on. After that, you could also adds the original onDelete() outside the ForEach

 ForEach {    } .onDelete(perform: deleteItems)

And both of them will work for you.