I'm trying to use @EnvironmentObject to control some aspects of my app. The issue I'm having is that one of my controllers can't access the environment object. I get the fatal error "No @ObservableObject of type Environment found".
I've searched other questions, and every solution I could find consisted of sending .environmentObject(myEnvironment) to the view in question. The problem is this is not a view, and I don't seem to have that option.
Also, in my SceneDelegate I send the environmentObject to the first view, so that is not the problem.
Here is my code.
First, I created a model to declare all my environment variables
Environment
struct Environment { var showMenu: Bool var searchText: String var location : Location init() { self.showMenu = false self.searchText = "" self.location = Location() } } Next I have a controller which purpose is to handle any actions related to the environment, right now it has none
EnvironmentController
import Foundation class EnvironmentController : ObservableObject { @Published var environment = Environment() } Now, in the SceneDelegate I call the NextDeparturesView, which in turn calls, the MapView.
MapView
import SwiftUI import MapKit //MARK: Map View struct MapView : UIViewRepresentable { @EnvironmentObject var environmentController: EnvironmentController var locationController = LocationController() func makeUIView(context: Context) -> MKMapView { MKMapView(frame: .zero) } func updateUIView(_ uiView: MKMapView, context: Context) { let coordinate = CLLocationCoordinate2D( latitude: environmentController.environment.location.latitude, longitude: environmentController.environment.location.longitude) let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1) let region = MKCoordinateRegion(center: coordinate, span: span) uiView.showsUserLocation = true uiView.setRegion(region, animated: true) } } You'll notice that in the MapView I call the LocationController, which is where the fatal error occurs
LocationController
import SwiftUI import MapKit import CoreLocation final class LocationController: NSObject, CLLocationManagerDelegate, ObservableObject { //MARK: Vars @EnvironmentObject var environmentController: EnvironmentController @ObservedObject var userSettingsController = UserSettingsController() //var declaration - Irrelevant code to the question //MARK: Location Manager var locationManager = CLLocationManager() //MARK: Init override init() { //more irrelevant code super.init() //Ask for location access self.updateLocation() } //MARK: Functions func updateLocation() { locationManager.delegate = self locationManager.desiredAccuracy = kCLLocationAccuracyBest if locationManager.responds(to: #selector(CLLocationManager.requestAlwaysAuthorization)){ locationManager.requestAlwaysAuthorization() } else { locationManager.startUpdatingLocation() } } //MARK: CLLocationManagerDelegate methods func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("Error updating location :%@", error) } func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { switch status { case .notDetermined: self.setDefaultLocation() break case .restricted: self.setDefaultLocation() break case .denied: self.setDefaultLocation() break default: locationManager.startUpdatingLocation() } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { let currentLocation = manager.location?.coordinate self.environmentController.environment.location.latitude = Double(currentLocation!.latitude) self.environmentController.environment.location.longitude = Double(currentLocation!.longitude) manager.stopUpdatingLocation() } //MARK: Other Functions func recenter() { locationManager.startUpdatingLocation() } func setDefaultLocation() { if self.$userSettingsController.userCity.wrappedValue == "" { self.environmentController.environment.location.latitude = 0.0 self.environmentController.environment.location.longitude = 0.0 } else { self.environmentController.environment.location.latitude = self.citiesDictionary[self.userSettingsController.userCity]!.latitude self.environmentController.environment.location.longitude = self.citiesDictionary[self.userSettingsController.userCity]!.longitude } } } So, this is where the fatal error occurs. For instance, my app usually calls setDefaultLocation() first, and the app is crashing there. Any idea what I am doing wrong, or how to solve it?
Thank you in advance.
EDIT
After much help from @pawello2222 I've solved my problem, however with some changes to the overall structure of my application.
I will accept his answer as the correct one, but I'll provide a list of things that I did, so anyone seeing this in the future might get nudged in the right direction.
- I was wrongly assuming that
ViewandUIViewRepresentablecould both access the@EnvironmentObject. OnlyViewcan. - In my
Environmentstruct, instead of aLocationvar, I now have aLocationController, so the same instance is used throughout the application. In myLocationControllerI now have a@Published var location: Location, so every View has access to the same location. - In structs of the type
ViewI create the@EnvironmentObject var environmentController: EnvironmentControllerand use theLocationControllerassociated with it. In other class types, I simply have aninitmethod which receives aLocationController, which is sent through theenvironmentController, for instance, when I callMapViewI do:MapView(locController: environmentController.environment.locationController)thus insuring that it is the same controller used throughout the application and the sameLocationthat is being changed. It is important that to use@ObservedObject var locationController: LocationControllerin classes such asMapView, otherwise changes won't be detected.
Hope this helps.
@EnvironmentObjectin views only.