Using an #Environment(\.managedObjectContext)—NSManagedObjectContext—in an init function - swift

I have this view.
struct AView: View {
#Environment(\.managedObjectContext) private var viewContext
#State var timestamp: Date
#State private var obj: MyObject
init(date: Date)
{
// this is fine
_timestamp = State(initialValue: date)
// this gives an error
_obj = State(initialValue: MyObject(context: viewContext))
}
var body: some View {
...
}
}
I need to initialize the object obj, which must be initialized with an NSManagedObjectContext, inside of the init function. However, when I try to use the code above, I get this error
Variable 'self.obj' used before being initialized
How do I initalize a variable in the init function that depends on an NSManagedObjectContext?

Related

SwiftUI: how to properly use init()?

I am trying to make use of init to call the fetchProducts function in my ViewModel class. When I add init though, I am getting the following 2 errors:
Variable 'self.countries' used before being initialized
and
Return from initializer without initializing all stored properties
The variable countries is binding though so there shouldn't need to be an initialized value in this view. Am I using init incorrectly?
struct ContentView: View {
#Namespace var namespace;
#Binding var countries: [Country];
#Binding var favLists: [Int];
#State var searchText: String = "";
#AppStorage("numTimeUsed") var numTimeUsed = 0;
#Environment(\.requestReview) var requestReview
#StateObject var viewModel = ViewModel();
init() {
viewModel.fetchProducts()
}
var body: some View {
}
}
Look at the initialiser that autocomplete gives you when you use ContentView…
ContentView(countries: Binding<[Country]>, favLists: Binding<[Int]>)
If you're creating your own initialiser, it will need to take those same parameters, e.g.
init(countries: Binding<[Country]>, favLists: Binding<[Int]>) {
_countries = countries
_favLists = favLists
viewModel.fetchProducts()
}
Alternatively, use the default initialiser, and instead…
onAppear {
viewModel.fetchProducts()
}

#EnvironmentObject is passed after init is called [duplicate]

This question already has answers here:
SwiftUI - How to pass EnvironmentObject into View Model?
(7 answers)
Closed last year.
#EnviromentObject is passed down when the body is called, so it doesn't yet exist during the initialization phase of the View struct. Ok, that is clear. The question is, how do you solve the following problem?
struct MyCollectionScreen: View {
// Enviroment
#EnvironmentObject var viewContext: NSManagedObjectContext
// Internal dependencies
#ObservedObject private var provider: CoreDataProvider
init() {
provider = CoreDataProvider(viewContext: viewContext)
}
}
The previous doesn't compile and throws the error:
'self' used before all stored properties are initialized.
That is because I am trying to use the #EnviromentObject object before it is actually set.
For this, I am trying a couple of things but I am not super happy with any of them
1. Initialize my provider after the init() method
struct MyCollectionScreen: View {
// Enviroment
#EnvironmentObject var viewContext: NSManagedObjectContext
// Internal dependencies
#ObservedObject private var provider: CoreDataProvider
var body: some View {
}.onAppear {
loadProviders()
}
mutating func loadProviders() {
provider = CoreDataProvider(viewContext: viewContext)
}
}
but that, doesn't compile either and you get the following error within the onAppear block:
Cannot use mutating member on immutable value: 'self' is immutable
2. Pass the viewContext in the init method and forget about Environment objects:
This second solution works, but I don't like having to pass the viewContext to most of my views in the init methods, I kind of liked the Environment object idea.
struct MyCollectionScreen: View {
// Internal dependencies
#ObservedObject private var provider: CoreDataProvider
init(viewContext: NSManagedObjectContext) {
provider = CoreDataProvider(viewContext: viewContext)
}
}
You can make viewContext in CoreDataProvider an optional, and then set it onAppear
struct MyCollectionScreen: View {
#EnvironmentObject var viewContext: NSManagedObjectContext
#ObservedObject private var provider = CoreDataProvider()
var body: some View {
Text("Hello")
.onAppear {
provider.viewContext = viewContext
}
}
Downside is that you'll now have an optional.
I think that what I would do is to create CoreDataProvider at the same time viewContext is created, and pass it as an environment object as well.

SWIFT5: Initilize only selected variables in init method

Is there a way to use init method without initializing Binding parameters? If they are not mentioned, I get error of "Initialize all parameters". If they are initialized, then it gives another error.
How can I avoid initialising them?
struct DetailView: View {
#Binding var isPresented: Bool
#Binding var quest: QuestObject
#State var selectedOption : String = ""
init()
{
//self.isPresented = $isPresented //I would like to skip initializing this paremeter
//self.quest = $quest //I would like to skip initializing this paremeter
self.selectedOption = "Apples"
}

How can I use the #Environment(\.calendar) in a View to initialize a #State variable?

I have a calendar view like so:
struct CalendarView: View {
#Environment(\.calendar) var calendar
#State var daysForMonthView: [DateInterval]
...
}
where i need to initialize the daysForMonthView array by using the #Environment(\.calendar). How can I do that?
Trying to do:
init() {
_daysForMonthView = State(initialValue: calendar.daysForMonthViewContaining(date: Date()))
}
produces a Variable 'self.daysForMonthView' used before being initialized -error.
You need to assign all the properties before you can access the #Environment. For this reason you can't use calendar to initialise daysForMonthView.
A possible solution is to use onAppear:
struct CalendarView: View {
#Environment(\.calendar) private var calendar
#State private var daysForMonthView: [DateInterval] = [] // assign empty
var body: some View {
//...
.onAppear {
daysForMonthView = calendar...
}
}
}
If for some reason you need calendar to be available in the init, you can pass it as a parameter in init:
struct ContentView: View {
#Environment(\.calendar) private var calendar
var body: some View {
CalendarView(calendar: calendar)
}
}
struct CalendarView: View {
private let calendar: Calendar
#State private var daysForMonthView: [DateInterval]
init(calendar: Calendar) {
self.calendar = calendar
self._daysForMonthView = .init(initialValue: calendar...)
}
//...
}
Note: the downside of this approach is that a change to the calendar will reinitialise the whole CalendarView.

How to pass a value from an EnvironmentObject to a class instance in SwiftUI?

I'm trying to assign the value from an EnvironmentObject called userSettings to a class instance called categoryData, I get an error when trying to assign the value to the class here ObserverCategory(userID: self.userSettings.id)
Error says:
Cannot use instance member 'userSettings' within property initializer; property initializers run before 'self' is available
Here's my code:
This is my class for the environment object:
//user settings
final class UserSettings: ObservableObject {
#Published var name : String = String()
#Published var id : String = "12345"
}
And next is the code where I'm trying to assign its values:
//user settings
#EnvironmentObject var userSettings: UserSettings
//instance of observer object
#ObservedObject var categoryData = ObserverCategory(userID: userSettings.id)
class ObserverCategory : ObservableObject {
let userID : String
init(userID: String) {
let db = Firestore.firestore().collection("users/\(userID)/categories") //
db.addSnapshotListener { (snap, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
for doc in snap!.documentChanges {
//code
}
}
}
}
Can somebody guide me to solve this error?
Thanks
Because the #EnvironmentObject and #ObservedObject are initializing at the same time. So you cant use one of them as an argument for another one.
You can make the ObservedObject more lazy. So you can associate it the EnvironmentObject when it's available. for example:
struct CategoryView: View {
//instance of observer object
#ObservedObject var categoryData: ObserverCategory
var body: some View { ,,, }
}
Then pass it like:
struct ContentView: View {
//user settings
#EnvironmentObject var userSettings: UserSettings
var body: some View {
CategoryView(categoryData: ObserverCategory(userID: userSettings.id))
}
}