Return 当@FetchRequest 谓词过滤器更改时,SwiftUI @FetchedResults 从子视图计数到父视图

Return SwiftUI @FetchedResults count from child view to parent when @FetchRequest predicate filter changes

父视图向子视图发送 FetchRequestBinding 的谓词过滤器字符串到 return FetchedResults 计数。

过滤器字符串是父视图的@State 属性。更改父项中的过滤器字符串会导致子项更新 FetchRequest.

我怎样才能让子视图用新的 FetchedResults 计数更新它收到的 Binding

请参阅 Child 中的代码注释了解我的尝试。

struct Parent: View {
    
    @State private var filter = "" // Predicate filter
    @State private var fetchCount = 0 // To be updated by child
        
    var body: some View {
        VStack {
            
            // Create core data test records (Entity "Item" with a property named "name")
            Button("Create Test Items") {
                let context = PersistenceController.shared.container.viewContext
                let names = ["a", "ab", "aab", "b"]
                for name in names {
                    let item = Item(context: context)
                    item.name = name
                    try! context.save()
                }
            }
            
            // Buttons to modify the filter to update the fetch request
            HStack {
                Button("Add") {
                    filter = filter + "a"
                }
                
                Button("Remove") {
                    filter = String(filter.prefix(filter.count-1 >= 0 ? filter.count-1 : 0))
                }
                
                Text("Filter: \(filter)")
            }
            
            Text("Fetch count in parent view (not ok): \(fetchCount)")

            Child(filter: filter, fetchCount: $fetchCount)
            
            Spacer()
        }
    }
}
struct Child: View {

    @FetchRequest var fetchRequest: FetchedResults<Item>
    @Binding var fetchCount: Int
    
    init(filter: String, fetchCount: Binding<Int>) {
        
        let predicate = NSPredicate(format: "name BEGINSWITH[c] %@", filter)
        
        self._fetchRequest = FetchRequest<Item>(
            entity: Item.entity(),
            sortDescriptors: [],
            predicate: filter.isEmpty ? nil : predicate
        )
        
        self._fetchCount = fetchCount
        
        // self.fetchCount = fetchRequest.count // "Modifying state during view updates, this will cause undefined behavior"
    }
    
    var body: some View {
        VStack {
            Text("Fetch count in child view (ok): \(fetchRequest.count)")
                .onAppear { fetchCount = fetchRequest.count } // Fires once, not whenever the predicate filter changes
            
            // The ForEach's onAppear() doesn't update the count when the results are reduced and repeatedly updates for every new item in results
            ForEach(fetchRequest) { item in
                Text(item.name ?? "nil")
            }
            //.onAppear { fetchCount = fetchRequest.count }
        }
    }
}

为了使您的代码正常工作,我需要做两件事。 首先,当您更改父视图中的过滤器时,您需要更新子视图中的过滤器。这个 使用传统的绑定在我的代码中完成。然后,由于 onAppear 只发生一次, 我使用 onReceive 来更新 fetchCount。这是我的代码:

编辑:根据@Jesse Blake 的建议

import Foundation
import SwiftUI


struct Child: View {
    @FetchRequest var fetchRequest: FetchedResults<Item>
    @Binding var fetchCount: Int
    var filter: String   // <-- here
    
    init(filter: String, fetchCount: Binding<Int>) {
        self.filter = filter   // <-- here
        
        let predicate = NSPredicate(format: "name BEGINSWITH[c] %@", filter)
        
        self._fetchRequest = FetchRequest<Item>(
            entity: Item.entity(),
            sortDescriptors: [],
            predicate: filter.isEmpty ? nil : predicate
        )
        
        self._fetchCount = fetchCount
    }
    
    var body: some View {
        VStack {
            Text("Fetch count in child view (ok): \(fetchRequest.count)")
            ForEach(fetchRequest) { item in
                Text(item.name ?? "nil")
            }
        }
    // in older ios
    //  .onChange(of: fetchRequest.count) { newVal in  // <-- here
    //      fetchCount = fetchRequest.count
    //  }
        .onReceive(fetchRequest.publisher.count()) { _ in  // <-- here
            fetchCount = fetchRequest.count
        }
    }
}

struct Parent: View {
    @Environment(\.managedObjectContext) private var viewContext
    
    @State private var filter = "" // Predicate filter
    @State private var fetchCount = 0 // To be updated by child
    
    var body: some View {
        VStack (spacing: 50) {
            // Create core data test records (Entity "Item" with a property named "name")
            Button("Create Test Items") {
                let context = PersistenceController.shared.container.viewContext
                let names = ["a", "ab", "aab", "b"]
                for name in names {
                    let item = Item(context: context)
                    item.name = name
                    try! context.save()
                }
            }
            // Buttons to modify the filter to update the fetch request
            HStack {
                Button("Add") {
                    filter = filter + "a"
                }
                Button("Remove") {
                    filter = String(filter.prefix(filter.count-1 >= 0 ? filter.count-1 : 0))
                }
                Text("Filter: \(filter)")
            }
            Text("Fetch count in parent view (not ok): \(fetchCount)")
            Child(filter: filter, fetchCount: $fetchCount)  // <-- here
            Spacer()
        }
    }
}