SwiftUI使用SwipeAction删除Realm列表项无动画该如何解决?
问题原因
- 原有实现中通过手动调用
objectWillChange.send()通知视图刷新,仅能触发全局刷新,无法提供列表元素的增量变化信息,SwiftUI无法生成单条元素删除的过渡动画 - 存储的
tasks是Realm的live集合(引用类型),内部元素变更不会改变引用本身,@Published无法自动触发更新,丢失了元素差异对比的基础 swipeAction的动画依赖于SwiftUI识别到列表的具体元素变更,全局刷新场景下不会触发动画
解决方案
分三步修改代码:
- 修改ViewModel的观察逻辑,每次集合变更时重新赋值冻结集合,移除手动的
objectWillChange调用,每次收到Realm集合变更通知时,将最新的冻结集合赋值给tasks,触发@Published自动更新,同时保留元素差异信息 - 调整ForEach的数据源,直接使用已经冻结的
tasks作为数据源,不需要二次调用freeze() - 可选:为List绑定动画修饰符,明确指定列表在
tasks变化时使用动画过渡
修改后的完整代码
ViewModel 代码
import Foundation import RealmSwift @MainActor class TasksViewModel: ObservableObject { // 存储冻结后的集合,每次变更都会赋值新的实例触发@Published @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.freeze() } } catch { print("error") } token = group.observe({ [weak self] (changes) in guard let self = self else { return } switch changes { case .error(_): break case .change(_, _): // 变更时重新赋值最新的冻结集合,触发@Published self.tasks = group.tasks.freeze() break case .deleted: break } }) } func archive(_ task: Task) { do { let realm = try Realm() try realm.write { realm.delete(task) } } catch { print(error) } } }
视图代码
import SwiftUI import RealmSwift struct TasksView: View { @StateObject var tasksViewModel: TasksViewModel = TasksViewModel() var body: some View { List { if let tasks = tasksViewModel.tasks { // 直接使用冻结后的tasks,不需要二次freeze ForEach(tasks, 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) } } } } // 可选:明确指定tasks变化时触发动画,和按钮内的withAnimation二选一即可 .animation(.easeInOut, value: tasksViewModel.tasks) } } struct TasksViewNew_Previews: PreviewProvider { static var previews: some View { TasksView() } }
数据模型部分无需修改,保持原有逻辑即可。
内容的提问来源于stack exchange,提问作者Francois
相关产品推荐
相关产品推荐

