【问题标题】:Return SwiftUI @FetchedResults count from child view to parent when @FetchRequest predicate filter changes当@FetchRequest 谓词过滤器更改时,将 SwiftUI @FetchedResults 计数从子视图返回到父视图
【发布时间】:2021-10-10 20:41:23
【问题描述】:

父视图向子视图发送FetchRequestBinding 的谓词过滤器字符串,以返回FetchedResults 计数。

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

如何让子视图使用新的FetchedResults 计数更新它收到的Binding

请参阅Child 中的代码 cmets 以了解我的尝试。

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

【问题讨论】:

    标签: swiftui count binding predicate fetchrequest


    【解决方案1】:

    我需要做两件事,才能让您的代码正常工作。 首先,当您更改父视图中的过滤器时,您需要更新子视图中的过滤器。这 使用传统绑定在我的代码中完成。然后,由于 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()
            }
        }
    }
    
      
    

    【讨论】:

    • .onRecieve(fetchRequest.publisher) 似乎是一个很好的解决方案,除了一种情况:当过滤器导致 FetchRequest 不返回任何结果时,父项中的计数不会更新。即使核心数据没有返回结果,你知道如何让发布者发布事件吗?
    • 试试这个:.onReceive(fetchRequest.publisher.count()) {...}。如果它适合你,我会更新我的答案。
    • 有效!重新更新您的答案,我不需要import Combine 或在Child 中使用Binding 作为过滤器(因为子视图不写入过滤器)。只需添加 .onRecieve 就可以了。
    • 更新了我的答案。如果这回答了您的问题,请用勾选标记将其标记为正确,谢谢。
    猜你喜欢
    • 2022-01-07
    • 2020-07-06
    • 1970-01-01
    • 1970-01-01
    • 2021-10-20
    • 2014-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多