【问题标题】:Cannot get @EnvironmentObject in Controller无法在控制器中获取@EnvironmentObject
【发布时间】:2020-09-24 01:59:03
【问题描述】:

我正在尝试使用 @EnvironmentObject 来控制我的应用程序的某些方面。我遇到的问题是我的一个控制器无法访问环境对象。我收到致命错误“未找到环境类型的 @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,而 NextDeparturesView 又调用 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,这是发生致命错误的地方

位置控制器

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 的大力帮助下,我已经解决了我的问题,但是对我的应用程序的整体结构进行了一些更改。

我会接受他的回答是正确的,但我会提供一份我做过的事情的清单,这样以后看到这个的人可能会被推向正确的方向。

  1. 我错误地假设ViewUIViewRepresentable 都可以访问@EnvironmentObject。只有View 可以。
  2. 在我的Environment 结构中,而不是Location var,我现在有一个LocationController,因此在整个应用程序中使用相同的实例。在我的LocationController 中,我现在有一个@Published var location: Location,因此每个视图都可以访问相同的位置。
  3. View 类型的结构中,我创建@EnvironmentObject var environmentController: EnvironmentController 并使用与之关联的LocationController。在其他类类型中,我只是有一个init 方法,它接收一个LocationController,它通过environmentController 发送,例如,当我调用MapView 时,我这样做:MapView(locController: environmentController.environment.locationController) 从而确保它是整个应用程序中使用的相同控制器和正在更改的相同Location。在MapView 等类中使用@ObservedObject var locationController: LocationController 很重要,否则将无法检测到更改。

希望这会有所帮助。

【问题讨论】:

标签: ios swift xcode environment-variables swiftui


【解决方案1】:

不要在Controller/ViewModel 中使用@EnvironmentObject(实际上是在View 之外的任何地方)。如果您想观察 Controller 中 Environment 的变化,您可以这样做:

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@EnvironmentObject 的形式注入到您的 ViewNOTViewModel) - 适用于您需要从 @ 访问 Environment 的情况仅限 987654335@。

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

3) Environment@EnvironmentObject 的形式注入您的View,然后传递给ViewModel

【讨论】:

  • 我仍然无法让它工作。现在它说调用 LocationController 的视图(MapView)没有对象。如果我在父视图上拥有环境对象,视图是否不应该访问它?例如,我的入口点视图有对象,我不能简单地拨打电话MapView()吗?我必须叫它MapView().environmentObject(envObject)吗?
  • 谢谢。我正在尝试执行选项 3。我编辑了我的问题以向您展示正在发生的事情。希望你能帮助我。
  • UIViewRepresentableView 不同。尝试在 init 中将 Environment 传递给您的 MapView。比如:MapView(environmentController: environmentController).
  • 我已经尝试过了,但我仍然无法将参数分配给 environmentController 或 locationController。我必须删除@EnvironmentObject 符号吗?我已经更新了代码,所以你可以明白我的意思。
  • 不能View 之外使用@EnvironmentObject 属性包装器。从 environmentController 的声明中删除 @EnvironmentObject 应该没问题。
猜你喜欢
  • 1970-01-01
  • 2021-01-28
  • 1970-01-01
  • 2020-10-12
  • 2016-09-22
  • 1970-01-01
  • 1970-01-01
  • 2018-12-01
  • 2015-02-04
相关资源
最近更新 更多