当 SwiftUI 列表选择更改时,如何防止核心数据获取请求重置其谓词?

How to prevent Core Data fetch request from resetting its predicate when SwiftUI List selection changes?

The sample app 具有 Garden 实体的核心数据模型,该模型与 Fruit 实体具有一对多关系。邀请用户在具有 selection 参数集的恒定编辑模式下使用 SwiftUI List 在花园里采摘水果。用户 selection 将反映在 Core Data 关系中。问题是,当用户搜索某物然后尝试 select 水果时,搜索将被重置。我假设这是预定义的行为,并想知道如何覆盖它以便搜索持续存在,即用户通过搜索设置的谓词仍然有效,并且列表仍待过滤。

struct FruitPicker: View {
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Fruit.name, ascending: true)],
        animation: .default)
    private var fruits: FetchedResults<Fruit>
    
    @Binding var selection: Set<Fruit>
    @State var searchText = ""
    
    var body: some View {
        List(fruits, id: \.self, selection: $selection) {
            Text([=10=].name ?? "")
        }
        .searchable(text: query)
        .environment(\.editMode, .constant(EditMode.active))
        .navigationTitle("Garden")
    }
    
    var query: Binding<String> {
        Binding {
            searchText
        } set: { newValue in
            searchText = newValue
            
            fruits.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "name CONTAINS[cd] %@", newValue)
        }
    }
}

Apple 开发者门户网站 a post 上有一个类似的问题。 post 中提供的解决方案是将谓词和排序描述符从父级传递到视图的初始化程序中。我试过了,但没有解决问题。

init(selection: Binding<Set<Fruit>>, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?) {
    _selection = selection
    let entity = Fruit.entity()
    
    _fruits = FetchRequest<Fruit>(entity: entity, sortDescriptors: sortDescriptors, predicate: predicate, animation: .default)
}
struct ContentView: View {
    @ObservedObject var garden: Garden
    
    var body: some View {
        NavigationView {
            NavigationLink("Pick Fruits in the Garden ") {
                FruitPicker(selection: $garden.fruits.setBinding(), sortDescriptors: [NSSortDescriptor(keyPath: \Fruit.name, ascending: true)], predicate: nil)
            }
        }
    }
}

我尝试了你的项目并设置了一些断点,问题是当做出选择时,ContentView 的主体被调用,它使用默认 FetchRequest 而不是带有搜索谓词的那个来初始化 FruitPicker。

在查看您的编码时,我注意到了一些 non-standard 事情。 PersistenceController 应该是一个结构而不是一个 ObservableObject(请参阅检查了核心数据的默认应用程序模板)。计算绑定的使用对我来说看起来很奇怪,但如果它有效的话就很酷。

要解决此问题,您可以将搜索和列表拆分为 2 个视图,以便使用新搜索词初始化列表,然后 FetchRequest 将始终正确,例如

struct FruitPickerSearch: View {
    @State var searchText = ""

    var body: some View {
        FruitPicker(searchText: searchText)
        .searchable(text: $searchText)
        .environment(\.editMode, .constant(EditMode.active))
        .navigationTitle("Garden")
    }
}

struct FruitPicker: View {
    private var fetchRequest: FetchRequest<Fruit>
    private var fruits: FetchedResults<Fruit> {
        fetchRequest.wrappedValue
    }
 
    init(searchText: String){
        let predicate = searchText.isEmpty ? nil : NSPredicate(format: "name CONTAINS[cd] %@", searchText)
        fetchRequest = FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Fruit.name, ascending: true)],
        predicate: predicate,
        animation: .default)
    }   

    var body: some View {
        List(fruits) { fruit in
            Text(fruit.name ?? "")
        }
    }
}