Reputation: 2836
In another question I have posted a solution for summary of CoreData with filters. The problem is: I cannot make those filters dynamic.
I have a CoreData entity: NPTransaction with attributes: date (Date) and value (Integer 64).
Working code for sum with static filter:
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)
}
}
Now if I try to add selectedDate variable like this:
// (...)
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>
// (...)
I am getting an error:
Cannot use instance member 'selectedDate' within property initializer; property initializers run before 'self' is available
So I have tried to move FetchRequest to init later in the code like this:
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 }
}
}
}
But now I am getting 2 errors. One on the line with reduce:
Value of type 'FetchRequest' has no member 'reduce'
And one on the line with Text("(sum)") :
Use of unresolved identifier 'sum'
How to solve this problem? How to sum CoreData FetchRequest with dynamic filters that users could change?
SOLUTION Based on answers from Asperi and Aspid, here is the final code that made dynamic filters work with sum of CoreData values:
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)
}
}
Upvotes: 1
Views: 1250
Reputation: 257593
I assume it was meant to make sum
as computable DashboardView
property (out of init
), as
var sum: Int64 {
fetchRequest.wrappedValue.reduce(0) { $0 + $1.value }
}
...
init() {
...
}
Upvotes: 1
Reputation: 699
If you want to perform fetch in Init, you need to get context first.
@Environment(\.managedObjectContext) var managedObjectContext
does not help you, becouse its not ready yet.
@FetchRequest
is a wrapper that allows you to do fatches when you need result, but it doesnt perferm fetch when you define it.
This code works for me, but im not realy sure whether getting context this way is realy correct and works good in all the cases:
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
}
}
But there is an alternative.
You can extract Sum view in subView, and pass date
variable through init
, where you use predicate. When you change @State
date, subView will initialize again and fetch will perform with new date. That looks much better.
Upvotes: 1