You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI使用SwipeAction删除Realm列表项无动画该如何解决?

问题原因
  • 原有实现中通过手动调用objectWillChange.send()通知视图刷新,仅能触发全局刷新,无法提供列表元素的增量变化信息,SwiftUI无法生成单条元素删除的过渡动画
  • 存储的tasks是Realm的live集合(引用类型),内部元素变更不会改变引用本身,@Published无法自动触发更新,丢失了元素差异对比的基础
  • swipeAction的动画依赖于SwiftUI识别到列表的具体元素变更,全局刷新场景下不会触发动画
解决方案

分三步修改代码:

  1. 修改ViewModel的观察逻辑,每次集合变更时重新赋值冻结集合,移除手动的objectWillChange调用,每次收到Realm集合变更通知时,将最新的冻结集合赋值给tasks,触发@Published自动更新,同时保留元素差异信息
  2. 调整ForEach的数据源,直接使用已经冻结的tasks作为数据源,不需要二次调用freeze()
  3. 可选:为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 04:42:00