我正在尝试使用@EnvironmentObject来控制我的应用程序的某些方面。我遇到的问题是我的一个控制器无法访问环境对象。我收到致命错误“找不到类型为Environment的@ObservableObject”。

我搜索了其他问题,发现的每个解决方案都包括将.environmentObject(myEnvironment)发送到相关视图。问题是这不是一个视图,我似乎没有那个选择。

另外,在我的SceneDelegate中,我将environmentObject发送到第一个视图,所以这不是问题。

这是我的代码。

首先,我创建一个模型来声明我的所有环境变量

环境

struct Environment {
        var showMenu: Bool
        var searchText: String
        var location : Location

        init() {
            self.showMenu = false
            self.searchText = ""
            self.location = Location()
        }
    }

接下来,我有一个控制器,其目的是处理与环境有关的所有操作,现在它没有

环境控制器
import Foundation

class EnvironmentController : ObservableObject {
    @Published var environment = Environment()
}

现在,在SceneDelegate中,我调用NextDeparturesView,后者依次调用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)
    }
}

您会注意到,在MapView中,我称为LocationController,这是发生致命错误的位置

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
        }
    }
}

因此,这是发生致命错误的地方。例如,我的应用程序通常首先调用setDefaultLocation(),而该应用程序在那里崩溃了。知道我在做什么错,或如何解决?

先感谢您。

编辑

经过@ pawello2222的大量帮助后,我解决了我的问题,但是对应用程序的整体结构进行了一些更改。

我会接受他的答案是正确的,但是我会提供一份我所做的事情的清单,以便将来看到此问题的人都可以向正确的方向轻推。
  • 我错误地认为ViewUIViewRepresentable都可以访问@EnvironmentObject。只有View可以。
  • 在我的Environment结构中,现在有了Location,而不是LocationController var,因此在整个应用程序中使用了相同的实例。在我的LocationController中,我现在有一个@Published var location: Location,因此每个视图都可以访问相同的位置。
  • View类型的结构中,我创建@EnvironmentObject var environmentController: EnvironmentController并使用与之关联的LocationController。在其他类类型中,我只是有一个init方法,该方法接收一个LocationController,该方法是通过environmentController发送的,例如,当我调用MapView时,我会执行:MapView(locController: environmentController.environment.locationController),从而确保它是整个应用程序中使用的同一控制器以及相同的Location正在改变。在@ObservedObject var locationController: LocationController之类的类中使用MapView很重要,否则将不会检测到更改。

  • 希望这可以帮助。

    最佳答案

    请勿在@EnvironmentObject / Controller中使用ViewModel(实际上是View之外的任何地方)。如果要观察Controller中环境的变化,可以执行以下操作:

    class Environment: ObservableObject {
        @Published var showMenu: Bool = false
        @Published var searchText: String = ""
        @Published var location : Location = Location()
    }
    
    class Controller: ObservableObject {
        @Published var showMenu: Bool
    
        private var environment: Environment
        private var cancellables = Set<AnyCancellable>()
    
        init(environment: Environment) {
            _showMenu = .init(initialValue: environment.showMenu)
            environment.$showMenu
                .receive(on: DispatchQueue.main)
                .sink(receiveValue: { [weak self] value in
                    self?.showMenu = value
                })
                .store(in: &cancellables)
        }
    }
    

    您还可以使用其他形式的依赖注入来注入Environment(甚至使用单例)。

    通常,有多种方法可以在视图中显示Environment变量(例如showMenu)(并刷新):

    1)Environment作为View注入到您的ViewModel中(而不是)作为@EnvironmentObject-在仅需要从Environment访问View的情况下。

    2)ViewModel订阅Environment(如上所述),并将其自己的变量发布到View。然后,无需在@EnvironmentObject中使用View

    3)Environment作为View注入到@EnvironmentObject中,然后传递给ViewModel

    关于ios - 无法在 Controller 中获取@EnvironmentObject,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/62205556/

    10-10 04:31