NEW: Start my new Ultimate Portfolio App course with a free Hacking with Swift+ trial! >>

Alert before onDelete list element

Forums > SwiftUI

I'm not able to trigger an Alert View from the onDelete closure, invoked deleting a list element, for example:

    private var viewUsers: some View {
        NavigationView {
            List {
                ForEach (self.usersViewModel.listOfUsers) {user in
                    VStack(alignment: .leading){
                        HStack {
                            Text(user.nomeUtente.capitalized).bold()
                            Text(user.cognomeUtente.capitalized).bold()
                        }
                        Text(user.mailUtente).foregroundColor(.gray)
                    }
                }
                .onDelete{ indexSet in
                    DispatchQueue.main.async() {
                        Alert(title: Text("title"), message: Text("message"), primaryButton: .default(Text("primaryButtonLabel")), secondaryButton: .cancel(Text("secondaryButtonLabel")))
                    }
                }
            }
            .navigationBarTitle(Text("Users"), displayMode: .inline)
            .navigationBarItems(trailing: EditButton())
        }
    }

Any suggestion ?

Thanks in advance.

   

Hi @giurobrossi, I think that a. views cannot be inserted via the .onDelete method in general (only logic) and b. that you need to introduce alerts through the .alert modifier, like so:

.alert(isPresented: $presentAlert) {
      Alert(...)
}

That would require you to introduce a presentAlert flag that is toggled to on in your .onDelete closure and toggled back off in the alert's handler.

   

That's fine, but the alert is shown after the execution of the onDelete callback: my purpose is to show the alert before the deletion of a list element. Unfortunately the indexSet of the deleting element is know only by the onDelete callback.

   

I'm using CoreData. The template for Master / Detail with CoreData has this in the Event.swift

extension Collection where Element == Event, Index == Int {
    func delete(at indices: IndexSet, from managedObjectContext: NSManagedObjectContext) {
        indices.forEach { managedObjectContext.delete(self[$0]) }
        do {
            try managedObjectContext.save()
        } catch {
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
    }
}

The content view has this:

ForEach(events, id: \.self) { event in
  NavigationLink(
    destination: DetailView(event: event)
  ) {
    Text("\(event.timestamp!, formatter: dateFormatter)")
    }
  }.onDelete { indices in
    self.events.delete(at: indices, from: self.viewContext)
  }

So I would think you can make your own function for deleting (a class function in User), and only go there if the user agrees from the alert. And put the alert in .onDelete like you did.

   

EDIT:

actually this should be even easier with

.alert(item: $deletedIndexSet) { indexSet in 
   return Alert(<YOUR ALERT>)  /// do some stuff with the indexSet here
}

this way you don't have to set the boolean and you also spare the force unwrap of the Optional (see here for reference: https://www.hackingwithswift.com/books/ios-swiftui/using-alert-and-sheet-with-optionals)

(btw. it's nice to see how I can improve my own answer due to learning more stuff 🥳)

original answer:


Hi @giurobrossi,

I've solved this issue (not having the indexSet after .onDelete is finished by having an

@State private var indexSetToDelete: IndexSet?

in my struct and setting this in the .onDelete:

@State private var showingAlert = false
@State private var deleteIndexSet: IndexSet?

<YOUR LIST>
.onDelete(perform: { indexSet in
    self.showingAlert = true
    self.deleteIndexSet = indexSet
})

.alert(isPresented: self.$showingAlert) {
    let indexSet = self.deleteIndexSet! /// you can force unwrap here because you only show the alert after .onDelete
    return Alert(<YOUR ALERT>)  /// do some stuff with the indexSet here
}

Hope that helps :)

(and I'm feeling great right now because this is the first time ever I shared my SwiftUI knowledge 🥰)

Greetings, Kevin

1      

Here is how I solved this. I wanted to write onConfirmedDelete anywhere I could call onDelete:

List {
    ForEach(model.seeds) { seed in
        Item(seed: seed)
    }
    .onMove { indices, newOffset in
        model.seeds.move(fromOffsets: indices, toOffset: newOffset)
    }
    .onConfirmedDelete(
        title: { indexSet in
            "Delete seed “\(model.seeds[indexSet.first!].name)”?"
        },
        message: "This cannot be undone.",
        action: { indexSet in
            model.seeds.remove(atOffsets: indexSet)
        }
    )
}

Here's how I implemented it:

extension DynamicViewContent {
    func onConfirmedDelete(title: @escaping (IndexSet) -> String, message: String? = nil, action: @escaping (IndexSet) -> Void) -> some View {
        DeleteConfirmation(source: self, title: title, message: message, action: action)
    }
}

struct DeleteConfirmation<Source>: View where Source: DynamicViewContent {
    let source: Source
    let title: (IndexSet) -> String
    let message: String?
    let action: (IndexSet) -> Void
    @State var indexSet: IndexSet = []
    @State var isPresented: Bool = false

    var body: some View {
        source
            .onDelete { indexSet in
                self.indexSet = indexSet
                isPresented = true
            }
            .alert(isPresented: $isPresented) {
                Alert(
                    title: Text(title(indexSet)),
                    message: message == nil ? nil : Text(message!),
                    primaryButton: .cancel(),
                    secondaryButton: .destructive(
                        Text("Delete"),
                        action: {
                            withAnimation {
                                action(indexSet)
                            }
                        }
                    )
                )
            }
    }
}

   

You may want to think about UX. As .onDelete has two steps already.

  1. User taps EditButton
  2. Taps Delete Button

or

  1. User swipe left
  2. Taps Delete Button or Swipe further left

So add another check might not be the best. If you just had a Button that delete then setting a 2nd check in case of accidental touches is a good idea but .onDelete has this already.

   

Hacking with Swift is sponsored by Essential Developer

SPONSORED From January 26th to 31st you can join a FREE crash course for iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a senior developer!

Save your spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.