SwiftUI animation in ForEach list

Viewed 267

I am trying and failing to produce an animation when the user taps the button to delete all entries in the ForEach below.

When the user confirms deletion of all entries in the array that populates the list (triggering the clearLog()function) the view just statically changes to the deleted state. I would like a simple animation such as the entries collapsing to the top, or swiping to the leading edge. Full code below.

I have tried making the RestRequestView struct conform to Identifiable and getting rid of the ./self with no success, and also just adding 'withAnimation' to the clearLog() call.

I haven't yet been able to post a minimal reproducible example.

The current behaviour is this one, with the list just popping empty with no animation.

undesired behaviour

The swipe to delete feature animates correctly.

swipetodelete

Can anyone help? Thanks in advance!

    struct RecentRequestsView: View {

    /// database of requests to be used in the RecentRequestsView
    @ObservedObject var requestLog: RequestLog
    
    /// clears the request log
    func clearLog() {
        requestLog.clearLog()
    }
    
    /// triggers the deletion confirmation alert
    func showAlert() {
        showingClearAlert = true
    }

    @State private var showingClearAlert = false

    let alertString = "Do you wish to delete all entries in this list?"

    var body: some View {
        NavigationView {
            if requestLog.requests.isEmpty {
                ZStack {
                    Color.gray
                        .opacity(0.1)
                    Text("Previous rest calculations will be shown here.")
                        .font(.title2)
                        .multilineTextAlignment(.center)
                        .padding()
                        .opacity(0.5)
                }.navigationBarTitle("Recent Rests", displayMode: .inline)
            } else {
                List {
                    ForEach(requestLog.requests.sorted().reversed(), id: \.self) { request in
                        RestRequestView(request: request)
                    }
                    .onDelete(perform: delete)
                    .transition(.slide)
                }.navigationBarTitle("Recent Rests", displayMode: .inline)
                    .toolbar {
                        Button(action: showAlert) {
                            Image(systemName: "trash")
                        }
                    }
                    .alert(alertString, isPresented: $showingClearAlert) {
                        Button("Cancel", role: .cancel) {
                            showingClearAlert = false
                        }
                        Button("Delete All", role: .destructive) {
                            clearLog()
                        }
                    }
            }
        }
    }

    /// Function called by the delete gesture which triggers the deletion of the specified rest request.
    /// - Parameter offsets: offsets sent by the swipe to delete gesture
    func delete(at offsets: IndexSet) {
        let elementToRemove = offsets.map { requestLog.requests.sorted().reversed()[$0]}.first

        guard let elementToRemove = elementToRemove else { return }

        requestLog.removeRequest(elementToRemove)
    }
}
1 Answers

You can create a tiny delay in clearLog().

Something like this should work

    Button("delete all", action: {
        for (n, obj) in requests.enumerated(){
            DispatchQueue.main.asyncAfter(deadline: .now() + (Double(n) * 0.1) ){
            viewContext.delete(obj)
            }
        }
    }) 

FYI for CoreData questions it is easiest to produce a Minimal Reproducible Example with the standard project. Everybody can create a store easily to help you out we don't need to know it all.

Related