【发布时间】:2021-10-13 23:09:24
【问题描述】:
我是 Swift 的新手,并试图通过一些(我认为!)简单的项目来更好地理解 Core Data。
我正在尝试使用@FetchRequest 属性包装器来列出属于给定父项的子项。这适用于大多数情况,但当父级关系是唯一会导致在添加新子项后显示子项的情况时则不适用。
我面临的问题似乎可以归结为使用 FetchRequest 装饰器中的谓词对 UUID 值进行的任何过滤,而不会像其他属性那样更新。我构建的最小示例(带有编辑)仅显示了使用 UUID 作为谓词时的行为,并避免了任何关系复杂化。
我的问题是,是否有一种“好”的方法可以使这种行为(驱动 FetchRequest 正确更新视图的谓词中的 UUID)工作,或者我缺少的东西,比如将其中一个部分注册为观察项目,还是我在错误的时间触发了更新?
我制作了一个示例来说明我遇到的难题。 注意:我正在使用 iOS 14.4 和 Xcode 12.4 我感谢这些不是最新的,但我的机器不支持 Catalina 之前的操作系统,所以我被困在这里,直到我买得起一个新的。
我的核心数据模型如下:
Item 实体有两个属性(uuid,UUID 类型)和一个 Int16 属性(number)。
该应用有两个按钮:+ 用于添加具有随机 number 值和固定 UUID 为 254DC... 的新项目和 5 用于添加项目,number 值为 5(和相同的、固定的 UUID)。
我用来驱动这个列表过滤项目实体的谓词:
- 匹配硬编码的 UUID 值*
- 或
number属性为 5
问题是,当添加(使用 + 按钮)一个新实体时,它应该显示在这个视图中,因为它的 uuid 属性匹配,它确实没有出现。如果您退出应用程序并返回它,它确实会出现(这证明谓词确实“匹配”了 UUID 值)。
点击 5 后(现在显示两行数字 5,如预期的那样):
点击+两次后(视图不变,应该有两行显示):
如果我们退出应用然后重新打开它,添加的项目(本例中为 300 和 304)仅现在可见:
问题的不同表述:为什么这些更新在 UUID 上运行不可靠,但与 number(Int16 类型)属性完美匹配?
按预期工作:
- 删除项目实体按预期工作,无论它们是否由于
number == 5或其 UUID 匹配而在列表中
注意事项:
- 我大量使用了
viewContext.perform,因为使用它解决了另一个未显示更新的故障。 - 我已经(基于this SO question/answer)已经尝试将行的每个元素包装为 ObservedObject。这也是在评论中提出的。已更新示例代码以执行此操作,这对此行为没有影响。 (元素不在 FetchRequest 生成的
items集合中,因此不要被此代码包装。) - 到处都是
try!。这只是为了使示例代码尽可能小。
查看代码
import SwiftUI
import CoreData
struct ItemList: View {
@Environment(\.managedObjectContext) private var viewContext
static let filterUUID = "254DC821-F654-4D45-BC94-CEEE12A428CB"
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.number, ascending: true)],
predicate: NSPredicate(format: "number == %@ OR uuid == %@", NSNumber(5), filterUUID))
var items : FetchedResults<Item>
var body: some View {
NavigationView {
List {
ForEach(items) { item in ItemRowView(item: item) }
.onDelete(perform: deleteItems)
}
.navigationTitle(Text(items.isEmpty ? "No items" : "Item count: \(items.count)"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button(action: addRandom) {
Label("Add Item", systemImage: "plus")
}
Button(action: addFive) {
Label("Add Five", systemImage: "5.circle.fill")
}
}
}
}
}
private func addItem(number: Int) {
viewContext.perform {
let newItem = Item(context: viewContext)
newItem.uuid = UUID(uuidString: ItemList.filterUUID)
newItem.number = Int16(number)
try! viewContext.save()
}
}
private func addRandom() { addItem(number: Int.random(in: 10...500)) }
private func addFive() { addItem(number: 5) }
private func deleteItems(offsets: IndexSet) {
viewContext.perform {
offsets.map { items[$0] }.forEach(viewContext.delete)
try! viewContext.save()
}
}
}
struct ItemRowView : View {
@ObservedObject var item : Item
var body : some View {
HStack {
Text("\(item.number)")
.font(.largeTitle)
Text(item.uuid?.uuidString.prefix(8) ?? "None")
}
}
}
我已经尝试了什么?
我花了两天时间搜索文档、StackOverflow 和各种博客,虽然有几件事乍一看似乎正是我正在寻找的答案,但我得到的示例越简单,它变得越混乱。
这开始是与父子获取相关的类似问题,但我已将我面临的实际问题缩小到更小的问题,这可以通过具有 UUID 且没有关系的单个实体来演示。
支持代码(App 和 PersistenceController)
import SwiftUI
import CoreData
@main
struct minimalApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ParentListView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "minimal")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}
【问题讨论】:
-
每个孩子也需要被包裹在一个@ObservedObject 中。只需使用从 ForEach 中获取项目的子内容创建一个行子视图。此外,如果您正确附加子项,则不需要子项获取请求,只需使用您已经拥有的被观察父项中的子项。
-
要解决集合问题,只需将集合包装在 Array(parentItem.children) 中
-
@loremipsum 感谢 cmets。已经尝试过
@ObservedObject包装(为此添加了注释和源链接),其中(可以理解,因为有问题的项目不在那里包装)在这里不起作用。已更新问题,重点关注基于 UUID 的过滤问题,避免实体关系分散注意力的复杂性。
标签: core-data swiftui nsfetchrequest