How to animate the deletion of an item in list using SwipeAction? Realm, Swiftui

Viewed 687

I'm trying to animate the deletion of an item when swiping left in a list but it's not working. I'm using Realm to save the tasks. The deletion is applied immediately without animation even though the action is wrapped inside withAnimation.

I noticed that when i use the onDelete modifier the animation is playing well but there is no animation with swipeAction

import SwiftUI
import RealmSwift

struct TasksView: View {
    
    @StateObject var tasksViewModel: TasksViewModel = TasksViewModel()
    
    var body: some View {
        
        List {
            if let tasks = tasksViewModel.tasks {
                ForEach(tasks.freeze(), id: \.id) { task in
                    Text(task.title)
                        .swipeActions(edge: .trailing, allowsFullSwipe: true) {
                            Button(role: .destructive) {
                                withAnimation {
                                    if let taskToDelete = task.thaw() {
                                        tasksViewModel.archive(taskToDelete)
                                    }
                                }
                            } label: {
                                Image("delete")
                            }
                            .tint(.red)
                        }
                }
            }
        }

    }
}

struct TasksViewNew_Previews: PreviewProvider {
    static var previews: some View {
        TasksView()
    }
}
import Foundation
import RealmSwift

@MainActor
class TasksViewModel: ObservableObject {

    @Published var tasks: List<Task>?
    
    var token: NotificationToken?

    init() {
        let realm = try! Realm()
        let group = Group()
        do {
            try realm.write {
                let tasks = [Task(title: "TaskA"),
                             Task(title: "TaskB"),
                             Task(title: "TaskC"),
                             Task(title: "TaskD"),]
                realm.add(group)
                group.tasks.append(objectsIn: tasks)
                self.tasks = group.tasks
            }
        } catch {
            print("error")
        }
        
        token = group.observe({ (changes) in
              switch changes {
              case .error(_):
                  break
              case .change(_, _):
                  self.objectWillChange.send()
                  break
              case .deleted:
                  break
              }
              
          })
        
    }
    
    func archive(_ task: Task) {
        do {

            let realm = try Realm()
            try realm.write {
                realm.delete(task)
            }

        } catch {
            print(error)
        }
    }
    
}
class Task: Object {
    @Persisted var id: String = UUID().uuidString
    @Persisted var title: String

    convenience init(title: String) {
        self.init()
        self.title = title
    }
}

class Group: Object, ObjectKeyIdentifiable {
    @Persisted var _id = ObjectId.generate()
    @Persisted var tasks = List<Task>()
}
2 Answers

As a followup to my comment on the original question, I here post my code where I encounter a similar issue with animating a deletion triggered using .swipeAction(). For better comparison, my View allows to switch between the custom deletion and a deletion using .onDelete(). The result shows the difference as such:

enter image description here

The code is based on Paul Hudson's example, but he only prints out a deletion message to showcase that the button is tapped.

// Friends.swift

import Foundation

class Friends: ObservableObject {
    @Published var list = ["Antoine", "Bas", "Curt", "Dave", "Erica"]
    
    func remove(friend: String) {
        if let index = list.firstIndex(of: friend) {
            list.remove(at: index)
        }
    }
    
    func remove(at indexSet: IndexSet) {
        list.remove(atOffsets: indexSet)
    }
}
// ContentView.swift

import SwiftUI

struct ContentView: View {
    @EnvironmentObject var friends: Friends
    
    @State private var usingOnDelete = false
    
    var body: some View {
        List {
            Section {
                Toggle("Using onDelete", isOn: $usingOnDelete)
            }
            
            Section {
                if usingOnDelete {
                    listUsingOnDelete
                } else {
                    listNotUsingOnDelete
                }
            }
        }
        .navigationTitle("Deletion test")
    }
    
    private var listUsingOnDelete: some View {
        ForEach(friends.list, id: \.self) { friend in
            Text(friend)
        }
        .onDelete { friends.remove(at: $0) }
    }
    
    private var listNotUsingOnDelete: some View {
        ForEach(friends.list, id: \.self) { friend in
            Text(friend)
            .swipeActions(allowsFullSwipe: true) {
                Button(role: .destructive) {
                    withAnimation {
                        friends.remove(friend: friend)
                    }
                } label: {
                    Label("Delete", systemImage: "trash.fill")
                }
                
                Button {
                    print("Muting conversation")
                } label: {
                    Label("Mute", systemImage: "bell.slash.fill")
                }
                .tint(.indigo)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            ContentView()
        }
        .environmentObject(Friends())
    }
}
// MainApp.swift

import SwiftUI

@main
struct MainApp: App {
    @StateObject var friends = Friends()
    
    var body: some Scene {
        WindowGroup {
            NavigationView {
                ContentView()
            }
            .environmentObject(friends)
        }
    }
}

This seems to be a bug in SwiftUI's List. Adding plain style to the list fixes this, but in my opinion it doesn't look as good.

.listStyle(.plain)

I've submitted feedback for this issue, as it can be reproduced with vanilla SwiftUI.

Related