'How to create a custom delete button without using the slide to delete that comes with swiftUI I am not using list, just using a foreach loop

Since, the onDelete and onMove are features of List/form I cannot use them when I have custom interfaces without them. I have used a VStack inside a ForEach. I am quite new to swiftUI and unsure on how I can implement custom code for onDelete and onMove.

Here's my code:

struct Trying: View {
    @State private var numbers = [0,1,2,3,4,5,6,7,8,9]
    var body: some View {
        NavigationView {
            VStack (spacing: 10) {
                ForEach(numbers, id: \.self) { number in
                    VStack {
                        Text("\(number)")
                    }
                    .frame(width: 50, height: 50)
                    .background(Color.red)
                }.onDelete(perform: removeRows)
            }
            .navigationTitle("Trying")
            .navigationBarItems(trailing: EditButton())
        }
    }
    
    func removeRows(at offsets: IndexSet) {
        numbers.remove(atOffsets: offsets)
    }
}

The way it works right now:

enter image description here



Solution 1:[1]

Here is a simple demo of possible approach to implement custom delete (of course with move it would be more complicated due to drag/drop, but idea is the same). Tested with Xcode 12 / iOS 14.

demo

struct DemoCustomDelete: View {
    @State private var numbers = [0,1,2,3,4,5,6,7,8,9]
    var body: some View {
        NavigationView {
            VStack (spacing: 10) {
                ForEach(numbers, id: \.self) { number in
                    VStack {
                        Text("\(number)")
                    }
                    .frame(width: 50, height: 50)
                    .background(Color.red)
                    .overlay(
                        DeleteButton(number: number, numbers: $numbers, onDelete: removeRows)
                    , alignment: .topTrailing)
                }.onDelete(perform: removeRows)
            }
            .navigationTitle("Trying")
            .navigationBarItems(trailing: EditButton())
        }
    }

    func removeRows(at offsets: IndexSet) {
        withAnimation {
            numbers.remove(atOffsets: offsets)
        }
    }
}

struct DeleteButton: View {
    @Environment(\.editMode) var editMode

    let number: Int
    @Binding var numbers: [Int]
    let onDelete: (IndexSet) -> ()

    var body: some View {
        VStack {
            if self.editMode?.wrappedValue == .active {
                Button(action: {
                    if let index = numbers.firstIndex(of: number) {
                        self.onDelete(IndexSet(integer: index))
                    }
                }) {
                    Image(systemName: "minus.circle")
                }
                .offset(x: 10, y: -10)
            }
        }
    }
}

backup

Solution 2:[2]

Based on @Asperi's answer, I just generalized it to accept any Equatable sequence.

struct DeleteButton<T>: View where T: Equatable {
  @Environment(\.editMode) var editMode

  let number: T
  @Binding var numbers: [T]
  let onDelete: (IndexSet) -> ()

  var body: some View {
    VStack {
        if self.editMode?.wrappedValue == .active {
            Button(action: {
                if let index = numbers.firstIndex(of: number) {
                    self.onDelete(IndexSet(integer: index))
                }
            }) {
                Image(systemName: "minus.circle")
            }
            .offset(x: 10, y: -10)
        }
    }
  }
}

Solution 3:[3]

I recently had the need to delete a row and I couldn't use a LIST. Instead I had a scroll view... But I was able to implement the edit to simulate the same onDelete behavior as if it was a list. Initially I couldn't get my code to work. It wasn't until I closely examined my implementation and experimented that I stumbled on why mine worked. I'm coding for an iPad so my NavigationView uses,

.navigationViewStyle(StackNavigationViewStyle())

Once I added this to the struct's NavigationView, when you click on the EditButton it activates editMode?.wrappedValue to .active / .inactive

Below is my implementation for the code sample above...

struct Trying: View {
    
    @State var num: Int = 0
    @Environment(\.editMode) var editMode
    
    @State private var numbers = [0,1,2,3,4,5,6,7,8,9]
    var body: some View {
        NavigationView {

            VStack {
                ForEach(numbers, id: \.self) { number in
                    HStack {
                        if editMode?.wrappedValue == .active {
                            Button(action: { num = number
                                removeRows(numbr: num)
                            }, label: {
                                Image(systemName: "minus.circle.fill")
                                    .foregroundColor(.red)
                            })
                        } // END IF editMode?wrappedValue == .active
                        Text("\(number)")
                            .frame(width: 50, height: 50)
                            .background(Color.red)
                    }

                }
//                .onDelete(perform: removeRows)
            }

            .navigationTitle("Trying")
            .navigationBarItems(trailing: EditButton())
        }
        // FOR SOME REASON THIS ALLOWS THE EditButton() to activate editMode  without a LIST being present.
        .navigationViewStyle(StackNavigationViewStyle())
    }
    
    func removeRows(numbr: Int) {
        print("removing \(numbr)")
    }
}

It looks like:

EditMode Simulation

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 rpetruzz