SwiftUI CoreData 过滤列表删除意外失败

Posted

技术标签:

【中文标题】SwiftUI CoreData 过滤列表删除意外失败【英文标题】:SwiftUI CoreData Filtered List Deletion Fails Unpredictably 【发布时间】:2020-10-12 21:41:27 【问题描述】:

我正在努力使用具有 SwiftUI 生命周期的 SwiftUI 应用程序,在该应用程序中,我创建了一个相当标准的 Core Data 列表并添加了一个搜索字段来过滤列表。两种观点——一种带有谓词,一种没有。未经过滤的列表生成按预期工作,包括滑动删除。过滤后的列表会适当地显示过滤后的列表,但在滑动删除时,我会得到完全不可预测的结果。有时该项目会消失,有时它会重新出现在列表中,有时会删除错误的项目。我无法辨别任何模式。

这是视图:

struct MyFilteredListView: View 
    @Environment(\.managedObjectContext) private var viewContext
    @Environment(\.horizontalSizeClass) var sizeClass

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \InvItem.name, ascending: true)],
        animation: .default) private var invItems: FetchedResults<InvItem>

    var fetchRequest: FetchRequest<InvItem>

    init(filter: String) 
        //there are actually a bunch of string fields included - just listed two here
        fetchRequest = FetchRequest<InvItem>(entity: InvItem.entity(), sortDescriptors: [], predicate: NSPredicate(format: "category1 CONTAINS[c] %@ || name CONTAINS[c] %@   ", filter, filter))
    

    var body: some View 
    
        let sc = (sizeClass == .compact)
    
        return List 
            ForEach(fetchRequest.wrappedValue, id: \.self)  item in
                NavigationLink(destination: InvItemDetailView(invItem: item)) 
                    InvItemRowView(invItem: item)
                        .frame(minWidth: 0, maxWidth: .infinity)
                        .frame(height: sc ? 100 : 200)
                        .padding(.leading, 10)
                //link
            
            .onDelete(perform: deleteInvItems)

        //list
    

    private func deleteInvItems(offsets: IndexSet) 
        withAnimation 

            offsets.map  invItems[$0] .forEach(viewContext.delete)

            do 
                try viewContext.save()
             catch 
                // Replace this - raise an alert
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            
        
    

Core Data 非常标准:

class InvItem: NSManagedObject, Identifiable 

extension InvItem 
    @NSManaged var id: UUID
    @NSManaged var category1: String?
    @NSManaged var name: String
    //bunch more attributes

    public var wrappedCategory1: String 
        category1 ?? "No category1"
    
    //other wrapped items
//extension inv item

extension InvItem 
    static func getAllInvItems() -> NSFetchRequest<InvItem> 
        let request: NSFetchRequest<InvItem> = InvItem.fetchRequest() as! NSFetchRequest<InvItem>
        let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
        request.sortDescriptors = [sortDescriptor]
        return request
    
//extension

我猜我不理解 deleteInvItems(offsets: IndexSet) 中的偏移行为,但是 我无法找到任何关于此的内容。相同的代码在未过滤列表中按预期工作。

任何指导将不胜感激。 Xcode 版本 12.2 beta (12B5018i) ios 14

第一次编辑: 我弄清楚了模式。显然 IndexSet 指的是整个实体,而不是过滤的项目。例如,未过滤列表有 10 个项目,过滤列表有 3 个项目。当我删除过滤列表中的第三项时,结果是删除未过滤列表中的第三项,因此除非这两者相同,否则第三项会重新出现在过滤列表中,并且未过滤列表中的第三项被删除来自核心数据。如果我再次删除过滤列表中的第三项,则原始列表中的第四项将被删除(因为第三项已经消失)。 所以问题就变成了 - 我如何获得对我想要的对象的引用 删除 - IndexSet 不起作用。

【问题讨论】:

【参考方案1】:

这可能是因为您的ForEach 正在使用wrappedValue,它可能会或可能不会更新。

https://developer.apple.com/documentation/swiftui/binding/wrappedvalue

我建议您查看创建新项目时提供的代码(您正在使用其中的一些),以便您可以调整并获得更准确的数据。

ForEach(items) item in

此外,在删除时,您将fetchRequest 中的offsetinvItems 中的项目混合在一起,只使用一个。

如果您希望能够动态过滤列表,我最好使用包裹在 ObservedObject 中的 FetchedResultsController https://www.youtube.com/watch?v=-U-4Zon6dbE

【讨论】:

我明白你对包装价值的看法。这是我能找到的能够动态更新搜索值的唯一方法。 FRC 的想法看起来很有希望 - 我会尝试的。 对于其他人:上述想法并没有完全解决我的问题。我去了老学校 - 对过滤后的项目进行了获取请求,创建了一个数组,读取要删除的对象的 id(在我的情况下为 UUID),获取该对象并将其删除。

以上是关于SwiftUI CoreData 过滤列表删除意外失败的主要内容,如果未能解决你的问题,请参考以下文章

过滤后的 SwiftUI CoreData 列表中的 Sum 属性

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

删除coredata列表项时SwiftUI App崩溃EXC_BAD_ACCESS错误

SwiftUI:如何将 CoreData 与动态过滤器相加?

浅谈SwiftUI 3.0新加入的CoreData动态FetchRequest过滤与排序特性

SwiftUI 列表在删除后继续显示核心数据项