6

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.

AdamSwift
  • 75
  • 6
  • 3
    Sometimes it helps to read the documentation: swipteActions - "When you add swipe actions, SwiftUI no longer synthesizes the Delete actions that otherwise appear when using the onDelete(perform:) method on a ForEach instance. You become responsible for creating a Delete action, if appropriate, among your swipe actions." – Yrb Dec 07 '21 at 01:25
  • Indeed I have not read this part of the documentation but I have noticed this behavior while testing. So what would you suggest for my issue? Should I quit using swipe actions or should I implement my own delete action? – AdamSwift Dec 07 '21 at 13:54
  • That is your decision. If you want to use `.swipeActions()`, you have to implement your own `.onDelete()` action. You don't get it for free any longer. – Yrb Dec 07 '21 at 14:41
  • Thanks. And if I want to avoid `.swipeActions()` then what would you suggest instead? As an alternative. – AdamSwift Dec 07 '21 at 16:03
  • 1
    You could add another button in the toolbar. Essentially, any gesture, button, etc is simply a way for the user to interact. If you close off one avenue, you need to use another. Heck, you could probably implement a "shake to edit", but it would be an unusual UI. This really strays into the realm of opinion on UI. And remember, you can use swipe actions, you just have to stick an action for delete action in there. – Yrb Dec 07 '21 at 16:47
  • Thank you. Indeed I’m trying lots of things and I think the most convenient and simplest way of doing this is using the swipe actions. So I will implement the delete function myself. The only “issue” here is that I need to restrict my app to iOS 15. – AdamSwift Dec 07 '21 at 16:51
  • 1
    You can also test for the iOS version in your app, and present sets of code. However, that was always the case, whether or not you could use `.onDelete()` with `.swipeActions()`. As soon as you put `.swipeActions()` in to your code, it was going to be for iOS 15+ only. Again, that is a developer choice. – Yrb Dec 07 '21 at 16:55
  • Thank you. I have chosen to stay on iOS 15 and implement my own delete function for swiping left, and it works fine, I'm happy with this solution. – AdamSwift Dec 08 '21 at 20:41

1 Answers1

0

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.

Legolas Wang
  • 1,951
  • 1
  • 13
  • 26