如何从 SwiftUI 列表和领域中删除数据

Posted

技术标签:

【中文标题】如何从 SwiftUI 列表和领域中删除数据【英文标题】:How to delete data from SwiftUI List and Realm 【发布时间】:2020-04-12 02:08:09 【问题描述】:

以下代码在 SwiftUI List 中正确显示 Realm 数据库中的所有“用户”。我的问题是滑动一行时删除记录。

当我滑动一行并点击删除按钮时,我立即收到 uncaught exception 错误,List 没有更新,但我知道正确的项目从 Realm 数据库中删除,因为我下次运行所选记录未显示的应用。

这是我的代码。

SwiftUI 代码

import RealmSwift

struct ContentView: View 

    @State private var allUsers: Results<User> = realm.objects(User.self)

    var body: some View 
        VStack
            Text("Second Tab")
            List
                ForEach(allUsers, id:\.self)  user in
                    HStack
                        Text(user.name)
                        Text("\(user.age)")
                    
                .onDelete(perform: deleteRow)
            
        
    

    private func deleteRow(with indexSet: IndexSet)
        indexSet.forEach ( index in
            try! realm.write 
                realm.delete(self.allUsers[index])
            
        )
    

领域模型

import RealmSwift

class User:Object
    @objc dynamic var name:String = ""
    @objc dynamic var age:Int = 0
    @objc dynamic var createdAt = NSDate()

    @objc dynamic var userID = UUID().uuidString
    override static func primaryKey() -> String? 
        return "userID"
    

错误

由于未捕获的异常“RLMException”而终止应用程序,原因:“索引 4 超出范围(必须小于 4)。”

当然,4 会根据 Realm 数据库中的项目数量而变化,在这种情况下,当我滑动并点击删除按钮时,我有 5 条记录。

我的期望是每次allUsers @State 变量更改时List 都会更新,我知道我的问题不是完全理解绑定的工作原理。

我做错了什么?

【问题讨论】:

在哪个确切的操作上,你得到了这个异常? 错误指向 AppDelegate - Thread 1: signal SIGABRT 第一个问题是 List 或 ForEach 中传递的数据必须符合 Identifiable 协议。此外,由于 Results 对象的性质,Realm 并不是非常适合 SwiftUI - 您可能暂时希望依靠通知来处理 UI 更新。一旦 Realm 有了 Frozen Objects,实现 Realm 功能就会更容易。 那么,如果我理解正确的话,您的意思是,到目前为止,Realm 和 SwiftUI 不能很好地协同工作?在 Realm 端或 SwiftUI 上学习通知?感谢您指出 List 或 ForEach 中传递的数据必须符合 Identifiable 协议这一事实。 这是我们迄今为止的经验。这并不是 Realm 的“问题”,因为结果按预期工作 - 它是与 SwiftUI 本质的交互。 SO上有许多similar questions,但没有不是解决方法的实际答案-其中大多数都与利用通知相关。 【参考方案1】:

我的期望是每次列表都会更新 allUsers @State 变量变化

这是正确的,但状态没有改变......以下应该可以工作

private func deleteRow(with indexSet: IndexSet)
    indexSet.forEach ( index in
        try! realm.write 
            realm.delete(self.allUsers[index])
        
    )
    self.allUsers = realm.objects(User.self)     // << refetch !!

注意:下面只是分配initial状态值

@State private var allUsers: Results<User> = realm.objects(User.self)

【讨论】:

不幸的是,按照您的建议从 Realm 删除记录后重新获取不起作用,我得到了同样的错误。 @fs_tigre 不幸的是,assigning initial state value 不正确,因为 Results 对象的性质。它们是实时更新的——这意味着领域结果对象与其底层对象保持实时连接,如果将对象添加到领域,结果将包括该对象。如果删除对象,则该对象将不再包含在这些结果中。可以观察结果对象的变化,然后可以在添加、修改或删除对象时采取行动。我很确定 @State 在这里不是正确的。 知道在哪里可以找到有关如何重组代码的信息吗? @fs_tigre 有时越简单越好,所以我只需将 allUsers 结果设为类 var,然后将观察者添加到该 var。在观察者块中,只需在发生变化时刷新 tableView。如果您的 tableView 中不需要动画 UI 元素,那就可以了。如果你这样做了,那么你可以利用观察者闭包在更细粒度的行级别控制 tableView 更新。 @fs_tigre,我越想越倾向于认为原因在其他地方......如果崩溃是由 SwiftUI 引起的,那将是一些 ios 起源的东西......比如UIKit 内部异常,或 swift 库...但是您声明它是 RLMException 并且这是领域内部异常,而通过提供的代码,一切都应该是正确的,因为在写入事务中执行删除...并且结果应该按照设计和更新立即记录。【参考方案2】:

这是使用 Realm 时非常常见的错误。它也发生在“传统”视图控制器中。

我找到的唯一可靠的解决方案,它大大提高了应用程序的稳定性,是始终使用界面项而不是领域对象。

此外,在现实生活中,我们需要做一些处理,从服务器加载头像图像,添加一些复选框、选择或其他任何东西,而且我们通常不需要显示对象的所有属性。不知何故,这是一种 MVVM 方法。

顺便说一句,你可以在循环之前调用realm.write,不确定,但我认为最好尽量减少上下文切换。

使用这种技术,无论是与 SwiftUI 还是 UIViewControllers,都可以很好地解决 RLM 崩溃问题。

struct ContentView: View 

    struct UserItem: Hashable 
        var id: String
        var name: String
        var age: Int
    

    private var allUsers: Results<User> = realm.objects(User.self)

    @State private var userItems: [UserItem] = []
    
    func updateItems() 
        userItems = allUsers.map 
            UserItem(id: $0.userID, name: $0.name, age: $0.age)
        
    
    
    var body: some View 
        VStack 
            Text("Second Tab")
            List
                ForEach(userItems, id:\.self)  user in
                    HStack
                        Text(user.name)
                        Text("\(user.age)")
                    
                
                .onDelete(perform: deleteRow)
                
            
        .onAppear() 
            updateItems()
        
    

    private func deleteRow(with indexSet: IndexSet)
        try! realm.write 
            indexSet.forEach 
                realm.delete(self.allUsers[$0])
            
        
        updateItems()
    

【讨论】:

以上是关于如何从 SwiftUI 列表和领域中删除数据的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式从 SwiftUI 列表中删除行并刷新列表视图?

如何从 SwiftUI 和 Realm 中另一个列表中的对象中添加和删除列表中的对象

如何从 SwiftUI 的详细信息视图(编辑项目视图)中删除核心数据条目?

如何从 WatchOS 的 SwiftUI 列表中删除分隔符?

如何从 Mac OS 中的 SwiftUI 列表中删除底部/顶部项目填充

SwiftUI - 如何在 CoreData 中删除实体中的行