mallow
mallow

Reputation: 2856

How to sum filtered Core Data in SwiftUI?

How to sum filtered Core Data values (using predicate) in SwiftUI app?

I have 1 entity named NPTransaction. It has 5 attributes and I would like to sum values from the attribute named value (Integer 64). Basically, it would sum up values of income in selected month (right now it's set to current Date() in the example code, but later on it will be set to whole current month).

I have found similar question regarding Swift, but I cannot make this code to work in my app. Link to related question: How to sum the numbers(Int16) of stored core data - Swift 3

Based on code from the link, I have following code right now:

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

I am just learning Swift and SwiftUI, so maybe there is a better and completely different way of solving this problem?

Upvotes: 1

Views: 3463

Answers (2)

mallow
mallow

Reputation: 2856

I have found similar problem in this question: Sum of (double) fetch request field I have modified the code a little and came up with this solution:

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

Upvotes: 3

clawesome
clawesome

Reputation: 1319

This should fix your current code, you're trying to reduce non existent variable called result which I believe you got from the code you linked. You're also making two fetches which shouldn't be necessary. Hopefully this helps.

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

Also, you can sum values from CoreData via an NSFetchRequest and NSExpression. Here's an example I found in this article: 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)")
}

Edit: I amended the sample code with your entity and column name.

Upvotes: 1

Related Questions