ios - 将 Realm 与 SwiftUI 结合使用时索引越界

标签 ios swift realm swiftui

我一直在尝试使用 SwiftUI,并一直在编写一个小型膳食计划/待办事项列表样式的应用程序。 我能够让 Realm 与 SwiftUI 一起工作,并编写了一个小包装对象来获取 Realm 更改通知以更新 UI。 这对于添加项目非常有用,并且 UI 会得到正确更新。但是,当使用滑动删除或其他方法删除项目时,我从 Realm 收到索引越界错误。

这是一些代码:

内容 View :

    struct ContentView : View {

    @EnvironmentObject var userData: MealObject
    @State var draftName: String = ""
    @State var isEditing: Bool = false
    @State var isTyping: Bool = false

    var body: some View {
        List {
            HStack {
                TextField($draftName, placeholder: Text("Add meal..."), onEditingChanged: { editing in
                    self.isTyping = editing
                },
                onCommit: {
                    self.createMeal()
                    })
                if isTyping {
                    Button(action: { self.createMeal() }) {
                        Text("Add")
                    }
                }
            }
            ForEach(self.userData.meals) { meal in
                NavigationLink(destination: DetailMealView(ingredientsObject: IngredientsObject(meal: meal))) {
                    MealRow(name: meal.name)
                }
            }.onDelete(perform: delete)
        }
        .navigationBarTitle(Text("Meals"))
    }

    func delete(at offsets: IndexSet) {
        guard let index = offsets.first else {
            return
        }
        let mealToDelete = userData.meals[index]
        Meal.delete(meal: mealToDelete)
        print("Meals after delete: \(self.userData.meals)")
    }
}

以及 MealObject 包装类:

final class MealObject: BindableObject {
    let willChange = PassthroughSubject<MealObject, Never>()

    private var token: NotificationToken!
    var meals: Results<Meal>

    init() {
        self.meals = Meal.all()
        lateInit()
    }

    func lateInit() {
        token = meals.observe { changes in
            self.willChange.send(self)
        }
    }

    deinit {
        token.invalidate()
    }
}

我能够将问题范围缩小到

   ForEach(self.userData.meals) { meal in
      NavigationLink(destination: DetailMealView(ingredientsObject: IngredientsObject(meal: meal))) {
      MealRow(name: meal.name)
     }
   }

似乎 self.userData.meals 没有更新,即使在检查 MealObject 中的更改通知时,它显示了正确的删除,并且 MealObject 中的膳食变量也正确更新。

*编辑:还要补充一点,删除确实发生了,当再次启动应用程序时,删除的项目消失了。 SwiftUI 似乎对状态感到困惑,并在调用 willChange 后尝试访问已删除的项目。

*编辑2:现在找到了一个解决方法,我实现了一种检查对象当前是否存在于Realm中的方法:

    static func objectExists(id: String, in realm: Realm = try! Realm()) -> Bool {
        return realm.object(ofType: Meal.self, forPrimaryKey: id) != nil
    }

这样调用

            ForEach(self.userData.meals) { meal in
                if Meal.objectExists(id: meal.id) {
                    NavigationLink(destination: DetailMealView(ingredientsObject: IngredientsObject(meal: meal))) {
                        MealRow(name: meal.name)
                    }
                }
            }.onDelete(perform: delete)

不是很漂亮,但它可以完成工作,直到我找到崩溃的真正原因。

最佳答案

使用 Realm Cocoa 5.0,您现在只需卡住传递给 ForEach 的任何集合:

ForEach(self.userData.meals.freeze()) { meal in
    NavigationLink(destination: DetailMealView(ingredientsObject: IngredientsObject(meal: meal))) {
        MealRow(name: meal.name)
    }
}
<小时/>

5.0 之前的答案:

SwiftUI 的 ForEach 的工作原理是,在发送 objectWillChange() 之后,它会迭代之前给出的集合和给出的新集合,然后对它们进行比较。这仅适用于不可变集合,但 Realm 集合是可变且实时更新的。此外,集合中的对象也会发生变化,因此将集合复制到数组中的明显解决方法也无法完全发挥作用。

我想出的最佳解决方法如下:

// helpers
struct ListKey {
    let id: String
    let index: Int
}
func keyedEnumeration<T: Object>(_ results: Results<T>) -> [ListKey] {
    return Array(results.value(forKey: "id").enumerated().map { ListKey(id: $0.1 as! String, index: $0.0) })
}

// in the body
ForEach(keyedEnumeration(self.userData.meals), id: \ListKey.id) { key in
    let meal = self.userData.meals[key.index]
    NavigationLink(destination: DetailMealView(ingredientsObject: IngredientsObject(meal: meal))) {
        MealRow(name: meal.name)
    }
}

这里的想法是预先提取主键数组并将其提供给 SwiftUI,以便它可以在不接触 Realm 的情况下区分它们,而不是尝试从实际已更新的“旧”集合中读取。

Realm 的 future 版本将支持卡住集合/对象,这将更适合 SwiftUI 想要的语义,但没有预计到达时间。

关于ios - 将 Realm 与 SwiftUI 结合使用时索引越界,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57160790/

相关文章:

ios - 如何使用按钮在 2 个部分之间移动单元格?

ios - 如何以编程方式设置 UIScrollView 以使用 autoLayout 水平滚动

ios - 找不到名为 `ProjectName` 的目标

ios - Realm swift 中的一对一关系

swift - 静态功能中的 Realm 第一用户

ios - 选择 List<T> 关系的所有记录到 Results<T>?

iphone - 是否可以从 iphone 静默发送位置数据(当然需要用户同意)?

ios - 为带盖子的盒子制作动画(场景套件)

ios - 如何使两个具有自动布局的标签具有相同的文本大小?

ios - JSON 序列化卡住了 UI