Reputation: 2856
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
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
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