如何在 SwiftUI 中汇总过滤的核心数据?

Posted

技术标签:

【中文标题】如何在 SwiftUI 中汇总过滤的核心数据?【英文标题】:How to sum filtered Core Data in SwiftUI? 【发布时间】:2020-01-10 12:16:25 【问题描述】:

如何在 SwiftUI 应用中对过滤后的 Core Data 值求和(使用谓词)?

我有 1 个名为 NPTransaction 的实体。它有 5 个属性,我想对名为 value (Integer 64) 的属性的值求和。基本上,它将汇总所选月份的收入值(现在它在示例代码中设置为 current Date(),但稍后将设置为整个当前月份)。

我发现了关于 Swift 的类似问题,但我无法让这段代码在我的应用程序中运行。 相关问题链接:How to sum the numbers(Int16) of stored core data - Swift 3

根据链接中的代码,我现在有以下代码:

import SwiftUI

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

   // This code in private var below produces many errors like:
   // Value of type 'AppDelegate' has no member 'managedObjectContext'
   // Use of undeclared type 'NSEntityDescription'
   // Use of undeclared type 'NSFetchRequest'
   // - so I assume that this linked question may be outdated or I should take different approach using SwiftUI
    private var income: Int64 
        let context = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
        let entityDesc: NSEntityDescription = NSEntityDescription.entity(forEntityName: "NPTransaction", in: context)!
        let request: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest()
        request.entity = entityDesc

        request.predicate = NSPredicate(format: "date == %@", selectedDate)
        let records = try! context.fetch(request)

        try! context.fetch(request) as! [NSManagedObject]
        let monthlyIncome = result.reduce(0)  $0 + ($1.value(forKey: "value") as? Int64 ?? 0) 
        return monthlyIncome
    

    var body: some View 
        NavigationView 
            VStack                     
               Text("Monthly income:")
               Text("\(income) USD")
                

// more code...

我只是在学习 Swift 和 SwiftUI,所以也许有更好的完全不同的方法来解决这个问题?

【问题讨论】:

NSExpressionDescription 很有用。例如***.com/questions/49099914/… 请记住 Date() 包含时间,因此您的谓词不太可能匹配任何对象。我将从删除谓词开始,以获得工作代码来计算所有值的总和。然后稍后再添加谓词 - 要获得“今天的总数”,您将需要相当于“日期 > 昨晚午夜和日期 谢谢,pbasdf,好点子。不过,我已经解决了这个日期谓词。我也欢迎仅针对 date > Date() 的解决方案。我仍然无法解决的问题是如何对数据求和。 Gagan_ios,谢谢,但您的链接中的问题没有得到解答。我不知道如何正确使用 NSExpressionDescription,也找不到有效的 SwiftUI 示例 【参考方案1】:

我在这个问题中发现了类似的问题: Sum of (double) fetch request field 我稍微修改了代码并提出了这个解决方案:

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)
    

【讨论】:

【参考方案2】:

这应该修复您当前的代码,您正在尝试减少名为 result 的不存在变量,我相信您是从链接的代码中获得的。您还进行了两次不必要的提取。希望这会有所帮助。

request.predicate = NSPredicate(format: "date == %@", selectedDate)
let records = try! context.fetch(request) as! [NSManagedObject]

let monthlyIncome = records.reduce(0)  $0 + ($1.value(forKey: "value") as? Int64 ?? 0) 
return monthlyIncome

此外,您可以通过 NSFetchRequest 和 NSExpression 对 CoreData 中的值求和。这是我在本文中找到的一个示例:Group by, Count and Sum in CoreData。

let keypathExp1 = NSExpression(forKeyPath: "value")
let expression = NSExpression(forFunction: "sum:", arguments: [keypathExp1])
let sumDesc = NSExpressionDescription()
sumDesc.expression = expression
sumDesc.name = "sum"
sumDesc.expressionResultType = .integer64AttributeType

let request = NSFetchRequest(entityName: "NPTransaction")
request.returnsObjectsAsFaults = false
request.propertiesToFetch = [sumDesc]
request.resultType = .dictionaryResultType

let monthlyIncome = try! context.fetch(request) as? Int64 

if let monthlyIncome = monthlyIncome 
    print("monthly income total: \(monthlyIncome)")

编辑:我用您的实体和列名修改了示例代码。

【讨论】:

谢谢,clawesome,但我仍然遇到很多错误。使用您的第一个代码,我得到Use of unresolved identifier 'request'。或Value of type 'AppDelegate' has no member 'managedObjectContext'Use of undeclared type 'NSFetchRequest'。使用第二个代码时,我得到了Use of unresolved identifier 'NSExpressionDescription'Use of unresolved identifier 'NSFetchRequest' 之类的东西。我想我在这里混合了太多代码。您的链接中的这个示例是否可以与 SwiftUI 一起使用?也许我将此代码放在文件的错误部分? 例如在线let sumDesc = NSExpressionDescription() 我收到一个错误:Use of unresolved identifier 'NSExpressionDescription'。我不知道该怎么办:/ 好的,我已经修复了一些错误,只需在文件开头添加import CoreData。但仍然在线let monthlyIncome = try! context.fetch(request) as? Int64 我收到Use of unresolved identifier 'context'。如果我尝试添加代码let context = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext,我会收到错误:Value of type 'AppDelegate' has no member 'managedObjectContext'。即使我在视图结构的开头有一行@Environment(\.managedObjectContext) var managedObjectContext(这适用于我使用核心数据的其他代码)。 抱歉,我假设您拥有的代码引用(例如let context)已经在工作。如果您收到Value of type 'AppDelegate' has no member 'managedObjectContext',那么您的应用程序委托很可能被重命名为AppDelegate 以外的其他名称,或者它没有managedObjectContext 变量,这可能是因为使用了SwiftUI。看起来你已经解决了这个问题。 谢谢,棒棒哒。 AppDelegate 具有此名称。我不知道是什么问题。我用另一种方式解决了这个问题。我不知道是否正确,但它有效。非常感谢你试图帮助我。万事如意!

以上是关于如何在 SwiftUI 中汇总过滤的核心数据?的主要内容,如果未能解决你的问题,请参考以下文章

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

如何在 SwiftUI 视图中更新核心数据信息

如何从 SwiftUI 的详细信息视图(编辑项目视图)中删除核心数据条目?

如何使用 swiftui 将文本字段存储到核心数据中

SwiftUI:如何从核心数据中的 TextField 中保存值

SwiftUI - 如何切换核心数据中的所有布尔值