3

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.

mallow
  • 2,368
  • 2
  • 22
  • 63
  • Would you show how do you declare `selectedDate` in view before init? – Asperi Jul 06 '20 at 05:25
  • `selectedDate` is declared in `class SelectedDate: ObservableObject {...}` in another view (DateView). I try to use it in TransactionsListView where I have fetchRequest init. I cannot use it in init, but I can use it inside body as `Text("\(self.selectedDate.startDateOfMonth)")` for example without a problem. I have updated my question with more details. – mallow Jul 06 '20 at 05:38

3 Answers3

1

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)
davidev
  • 7,694
  • 5
  • 21
  • 56
  • 1
    Thank you very much. I figured that I could pass arguments like this: `TransactionsListView(startDateOfMonth: self.selectedDate.startDateOfMonth, endDateOfMonth: self.selectedDate.endDateOfMonth)`, but your version is even better! :) – mallow Jul 06 '20 at 12:52
0

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.

Jim lai
  • 1,224
  • 8
  • 12
  • Thank you, but how to do it? You mean I should not put fetchrequest inside init, but make it a var or lazy var? I use init because I found it work in some tutorials. Still learning Swift and SwiftUI and inits are something I still don’t understand very well – mallow Jul 06 '20 at 07:16
  • Yes, in the scope of computed or lazy var, you have access to self. Swift has very strict 2-phase init process. It's often easier to do stuff after init, not during init. You should be able to find a lot of examples regarding computed, lazy, 2-phase init online. – Jim lai Jul 06 '20 at 08:37
0

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.

malhal
  • 26,330
  • 7
  • 115
  • 133