mallow
mallow

Reputation: 2836

SwiftUI: ObservableObject doesn't work with fetchRequest init

I have an ObservableObject declared like this in my DateView:

import SwiftUI

    class SelectedDate: ObservableObject {
        @Published var selectedMonth: Date = Date()
        
        var startDateOfMonth: Date {
            let components = Calendar.current.dateComponents([.year, .month], from: self.selectedMonth)
            let startOfMonth = Calendar.current.date(from: components)!
            return startOfMonth
        }
    
        var endDateOfMonth: Date {
            var components = Calendar.current.dateComponents([.year, .month], from: self.selectedMonth)
            components.month = (components.month ?? 0) + 1
            let endOfMonth = Calendar.current.date(from: components)!
            return endOfMonth
        }
    }

struct DateView: View {
// other code
}

I can access startDateOfMonth and endDateOfMonth in other views like this:

Text("\(self.selectedDate.startDateOfMonth)")

But I have a problem when I try to use those variables from ObservableObject inside a fetchRequest initializer in my TransactionsListView:

    import SwiftUI
    
    struct TransactionsListView: View {
    
    @Environment(\.managedObjectContext) var managedObjectContext
        var fetchRequest: FetchRequest<NPTransaction>
        var transactions: FetchedResults<NPTransaction> { fetchRequest.wrappedValue }
    @EnvironmentObject var selectedDate: SelectedDate

    // other code
            init() {
                    fetchRequest = FetchRequest<NPTransaction>(entity: NPTransaction.entity(), sortDescriptors: [
                        NSSortDescriptor(keyPath: \NPTransaction.date, ascending: false)
                    ], predicate: NSPredicate(format: "date >= %@ AND date < %@", self.selectedDate.startDateOfMonth as NSDate, self.selectedDate.endDateOfMonth as NSDate))
            }
    
    var body: some View {
    // other code
    }
    }

I am getting an error:

Variable 'self.fetchRequest' used before being initialized

What ma I doing wrong? I have tried to use those variables inside init without self. Same error. The only way it worked was if those startDateOfMonth and endDateOfMonth were not in ObservableObject, but as a vars in ancestor view which I was passing as parameters to my view with fetchRequest init. But I have few views like this and wanted to use ObservableObject, not to pass the same parameters to subviews all the time.

Upvotes: 3

Views: 2430

Answers (3)

malhal
malhal

Reputation: 30549

You can pass it in as a param to the View:

import SwiftUI

struct TransactionsListView: View {

    @Environment(\.managedObjectContext) var managedObjectContext
    @FetchRequest var transactions: FetchedResults<NPTransaction>

    init(selectedDate: SelectedDate) {
        _transactions = FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \NPTransaction.date, ascending: false)], predicate: NSPredicate(format: "date >= %@ AND date < %@", selectedDate.startDateOfMonth as NSDate, selectedDate.endDateOfMonth as NSDate))
    }

    var body: some View {
    // other code
    }
}

Store the selectedDate as @State in the superview and recreate the TransactionsListView with the current value.

Upvotes: 0

davidev
davidev

Reputation: 8517

The problem is that you can not use @EnvironmentObject in the init(). The possible option here is to pass the EnvironmentObject as parameter to the View. Then use that parameter inside the init. Here is the constructor for the view, then you can access start and endTime with local variable selectedDate.

init(selectedDate : SelectedDate) 
{
   //now you can access selectedDate here and use it in FetchRequest
   fetchRequest = FetchRequest<NPTransaction>(entity: NPTransaction.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \NPTransaction.date, ascending: false)], predicate: NSPredicate(format: "date >= %@ AND date < %@", selectedDate.startDateOfMonth as NSDate, selectedDate.endDateOfMonth as NSDate))
}

Where you call that view, pass the EnvironmentObject as parameter.

                                    //Declared as Environment Object
TransactionsListView(selectedDate : self.selectedDate)

Upvotes: 1

Jim lai
Jim lai

Reputation: 1409

Swift guarantees all properties are initialized after init.

When you access object property, it is assumed that object is already initialized, which is not the case when you access it in the middle of the init.

What happens is that when you initialize fetchRequest, you access self.selectedDate (same to compiler whether you add self or not), which suggests that self is initialzed, which implies that, fetchRequest, as one of the self's properties, is ready to use. Hence the compiler error:

Variable 'self.fetchRequest' used before being initialized

Make fetchRequest a computed or lazy property should work. (not too familiar with @FetchRequest)

Hope this helps.

Upvotes: 0

Related Questions