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

发布于 2025-01-16 21:58:27 字数 2218 浏览 2 评论 0原文

示例应用的核心数据模型为 GardenFruit 实体具有一对多关系的实体。邀请用户在恒定编辑模式下使用 SwiftUI List 在花园中采摘水果,并设置选择参数。用户的选择将反映在核心数据关系中。问题是,当用户搜索某些内容然后尝试选择水果时,搜索将被重置。我假设这是预定义的行为,并想知道如何覆盖它,以便搜索持续存在,即用户通过搜索设置的谓词仍然处于活动状态,并且列表仍然需要过滤。

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($0.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 开发者门户网站上的帖子涉及到了类似的问题。帖子中提供的解决方案是传入谓词并将描述符从父级排序到视图的初始值设定项中。我已经尝试过了,但并不能解决问题。

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 
              

The sample app has a Core Data model of Garden entity that has a to-many relationship with Fruit entity. User is invited to pick fruits in the garden using a SwiftUI List in constant edit mode with a selection parameter set. The user selection will be reflected in the Core Data relationship. The issue is that when the user searches for something and then attempts to select a fruit, the search is reset. I'm assuming this is predefined behavior and wondering how to override it so the search persists i.e. the predicate that the user set via search is still active, and the list remains to be filtered.

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($0.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)
        }
    }
}

There is a post on the Apple developer portal that goes into a somewhat similar issue. The solution that is offered in the post is to pass in a predicate and sort descriptors from the parent into the view's initializer. I've tried that and it does not solve the issue.

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)
            }
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

三生路 2025-01-23 21:58:27

我尝试了你的项目,设置了一些断点,问题是当进行选择时,ContentView的主体被调用,它使用默认的FetchRequest<初始化FruitPicker /code> 而不是带有搜索谓词的。

我还注意到您的代码中有一些非标准的东西。 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 ?? "")
        }
    }
}

I tried your project, made some breakpoints and the problem is when a selection is made, ContentView's body is called, which inits FruitPicker with the default FetchRequest instead of the one with the search predicate.

I also noticed some non-standard things in your code. PersistenceController should be a struct instead of an ObservableObject (see the default app template with core data checked). The use of computed bindings looks odd to me but fine if it works.

To fix the problem you could break up the search and the list into 2 Views, so that the List is init with the new search term and then the FetchRequest will always be correct, e.g.

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 ?? "")
        }
    }
}
混吃等死 2025-01-23 21:58:27

因为每次您在列表中选择一个项目时,视图都会更新,从而导致您的谓词重置为其初始状态。

另一种方法可能是使用 Equatable 协议。让您的子视图符合此协议,设置您自己的规则,并在父视图上使用 EquatableView() 包装您的子视图。关于这个问题,你可以参考我的另一篇文章:Why does @EnvironmentObject Force Re-initialization of View?

Because every time you select an item in the list, the view will get updated, thus cause your predicate to reset to its initial state.

Another approach would probably be using the Equatable protocol. Make your subview conforms to this protocol, set your own rules, and wrap your subview with EquatableView() on the parent view. You can refer to my other post regarding this problem: Why Does @EnvironmentObject Force Re-initialization of View?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文