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

Posted

技术标签:

【中文标题】SwiftUI:如何将 CoreData 与动态过滤器相加?【英文标题】:SwiftUI: How to sum CoreData with dynamic filters? 【发布时间】:2020-01-13 12:09:13 【问题描述】:

在另一个问题中,我发布了一个使用过滤器汇总 CoreData 的解决方案。 问题是:我无法使这些过滤器动态化。

我有一个 CoreData 实体:NPTransaction,其属性:date (Date)value (Integer 64)

使用静态过滤器求和的工作代码:

import SwiftUI
import CoreData

struct DashboardView: View 
    @Environment(\.managedObjectContext) var managedObjectContext
    // FetchRequest with predicate set to "after now" 
    @FetchRequest(entity: NPTransaction.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \NPTransaction.value, ascending: false)], predicate: NSPredicate(format: "date > %@", Date() as NSDate)) var fetchRequest: FetchedResults<NPTransaction>

    // sum results using reduce
    var sum: Int64 
        fetchRequest.reduce(0)  $0 + $1.value 
    

    var body: some View 
        NavigationView 
            VStack(alignment: .leading) 

                HStack 
                    VStack(alignment: .leading) 
                        Text("sum")
                        Text("\(sum)")
                            .font(.largeTitle)

                    
                    Spacer()
                
                .padding()

                Spacer()
            .navigationBarTitle("Title")
        
    



struct DashboardView_Previews: PreviewProvider 
    static var previews: some View 
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return DashboardView().environment(\.managedObjectContext, context)
    

现在,如果我尝试像这样添加 selectedDate 变量:

// (...)

struct DashboardView: View 
    @Environment(\.managedObjectContext) var managedObjectContext
    @State var selectedDate = Date()

    @FetchRequest(entity: NPTransaction.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \NPTransaction.value, ascending: false)], predicate: NSPredicate(format: "date > %@", selectedDate as NSDate)) var fetchRequest: FetchedResults<NPTransaction>

// (...)

我收到一个错误:

不能在属性初始化器中使用实例成员“selectedDate”; 属性初始化器在“self”可用之前运行

所以我尝试将 FetchRequest 移动到稍后在代码中进行初始化,如下所示:

import SwiftUI
import CoreData

struct DashboardView: View 
    @Environment(\.managedObjectContext) var managedObjectContext
    @State var selectedDate = Date()

    var fetchRequest: FetchRequest<NPTransaction>

    var body: some View 
        NavigationView 
            VStack(alignment: .leading) 

                HStack 
                    VStack(alignment: .leading) 
                        Text("sum")
                        Text("\(sum)")
                            .font(.largeTitle)

                    
                    Spacer()
                
                .padding()

                Spacer()
            .navigationBarTitle("Title")
        
    

    init() 
        fetchRequest = FetchRequest<NPTransaction>(entity: NPTransaction.entity(), sortDescriptors: [
            NSSortDescriptor(keyPath: \NPTransaction.value, ascending: false)
        ], predicate: NSPredicate(format: "date >= %@", Date() as NSDate))

        var sum: Int64 
            fetchRequest.reduce(0)  $0 + $1.value 
        
    


但现在我收到 2 个错误。一个与reduce一致:

“FetchRequest”类型的值没有成员“reduce”

还有一个与 Text("(sum)") :

使用未解析的标识符“sum”

如何解决这个问题?如何将 CoreData FetchRequest 与用户可以更改的动态过滤器相加?


解决方案 根据 Asperi 和 Aspid 的回答,这里是使动态过滤器与 CoreData 值的总和一起工作的最终代码:

DashboardView.swift

import SwiftUI

struct DashboardView: View 
    @State var selectedDate = Date()

    var body: some View 
        NavigationView 
            VStack(alignment: .leading) 

                // selectedDate change is controlled in another DateView
                DateView(selectedDate: $selectedDate)

                // selectedDate is passed to init in DashboardViewSum
                DashboardViewSum(selectedDate: selectedDate)

                Spacer()
            .navigationBarTitle("Title")
        
    


struct DashboardView_Previews: PreviewProvider 
    static var previews: some View 
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return DashboardView().environment(\.managedObjectContext, context)
    

DashboardViewSum.swift

import SwiftUI

struct DashboardViewSum: View 

    @Environment(\.managedObjectContext) var managedObjectContext   
    var fetchRequest: FetchRequest<NPTransaction>
    var sum: Int64 
        fetchRequest.wrappedValue.reduce(0)  $0 + $1.value 
    

    var body: some View 
        HStack 
            VStack(alignment: .leading) 
                Text("sum")
                Text("\(sum)")
                    .font(.largeTitle)

            
            Spacer()
        
        .padding()
    

    // selectedDate is passed from DashboardView and makes predicate dynamic. Each time it is changed, DashboardViewSum is reinitialized
    init(selectedDate: Date) 
        fetchRequest = FetchRequest<NPTransaction>(entity: NPTransaction.entity(), sortDescriptors: [
            NSSortDescriptor(keyPath: \NPTransaction.value, ascending: false)
        ], predicate: NSPredicate(format: "date >= %@", selectedDate as NSDate))
    



struct DashboardViewSum_Previews: PreviewProvider 
    static var previews: some View 
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return DashboardViewSum(selectedDate: Date()).environment(\.managedObjectContext, context)
    

【问题讨论】:

【参考方案1】:

如果要在 Init 中执行 fetch,则需要先获取上下文。 @Environment(\.managedObjectContext) var managedObjectContext 对你没有帮助,因为它还没有准备好。 @FetchRequest 是一个包装器,它允许您在需要结果时进行填充,但在您定义它时它不会执行获取。 这段代码对我有用,但我不确定以这种方式获取上下文是否真的正确并且在所有情况下都很好用:

init()
    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContaner.viewContext
    let fetchRequest: NSFetchRequest<SomeType> = SomeType.fetchRequest
    //... some order and filter
    if let result = try? context.fetch(fetchRequest)
        //result is [someClass] - do what you need
    

但是还有一个选择。 您可以在 subView 中提取 Sum 视图,并将 date 变量传递给您使用谓词的init。当您更改 @State 日期时,subView 将再次初始化,并且 fetch 将以新日期执行。这样看起来好多了。

【讨论】:

非常感谢您,阿斯皮德!我无法让你的代码工作,但你的替代想法帮助我解决了这个问题。杰出的!我已将您的想法与 Asperi 对我的代码的修复一起使用,现在它可以工作了:) 我已经编辑了我的问题,以根据您和 Asperi 的答案提供有效的解决方案。谢谢!【参考方案2】:

我认为它的目的是将sum 设为可计算的DashboardView 属性(在init 之外),作为

var sum: Int64 
    fetchRequest.wrappedValue.reduce(0)  $0 + $1.value 


...
init() 
   ...

【讨论】:

非常感谢您,Asperi!你的代码帮助我摆脱了错误,让我离解决方案更近了一步。 Aspid 的想法与您的修复相结合,使其发挥了作用。我已经编辑了我的问题,以根据您和 Aspid 的回答提供可行的解决方案。谢谢!

以上是关于SwiftUI:如何将 CoreData 与动态过滤器相加?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 SwiftUI 中的颜色类型存储到 CoreData 中?

保存数据时出现问题,将 CoreData 与 SwiftUI 一起使用

如何在 SwiftUI 中从 FetchedResults (CoreData) 中获取 NSOrderedSet

浅谈CoreData海量数据(实时动态添加和删除)在SwiftUI列表中分页显示的原理及实现

SwiftUI 中的动态过滤器(谓词)

如何让 SwiftUI 与 Core Data 一起工作(在启动项目后)?