SwiftUI中如何将EnvironmentObjects传入AppDelegate并正确使用?
问题描述
我尝试在AppDelegate中通过addStateDidChangeListener检查Firebase用户的token有效性,代码如下:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions) FirebaseApp.configure() Auth.auth().useAppLanguage() Auth.auth().addStateDidChangeListener { auth, user in if let user = user { user.getIDTokenResult { result, error in if let error = error { print("Could not get the ID token result: \(error)") self.sessionExpired() } if let expirationDate = result?.expirationDate { if Date() >= expirationDate { print("Login session has expired.") self.sessionExpired() } } else { print("Could not get the expiration date from the ID token result.") self.sessionExpired() } } } else { print("No user is signed in.") } } return true }
当token无效或无用户登录时,会弹出提示重新登录的UIAlertController:
func sessionExpired() { let alertController = UIAlertController(title: "Session Expired.", message: "Your login session has expired. Please sign in again.", preferredStyle: .alert) let okAction = UIAlertAction(title: "OK", style: .default) { action in FirebaseFunctions.signOutOfFirebase(saveAt: self.viewContext, with: self.environmentObjects, with: self.mapInformation, deleteCoreData: true) } alertController.addAction(okAction) UIApplication.shared.windows.first { $0.isKeyWindow }?.rootViewController?.present(alertController, animated: true) }
点击OK按钮调用FirebaseFunctions.signOutOfFirebase时,出现以下错误:
Fatal error: No ObservableObject of type EnvironmentObjects found. A View.environmentObject(_:) for EnvironmentObjects may be missing as an ancestor of this view.
我的应用入口代码如下:
@main struct MainApp: App { @UIApplicationDelegateAdaptor var appDelegate: AppDelegate @StateObject var environmentObjects = EnvironmentObjects() @StateObject var networkMonitor = NetworkMonitor() @StateObject var locationManager = LocationManager() @StateObject var mapInformation = MapInformation() @StateObject var popRoot = PopRoot() ... code
请问如何将这些环境对象传入AppDelegate?
解决方案
方法1:从MainApp直接注入环境对象到AppDelegate
EnvironmentObject是SwiftUI专属的状态管理对象,AppDelegate属于UIKit生命周期,无法直接获取SwiftUI视图层级中的环境对象,但可以通过在AppDelegate中声明属性,从MainApp主动注入:
- 在
AppDelegate中添加对应属性:
class AppDelegate: UIResponder, UIApplicationDelegate { var environmentObjects: EnvironmentObjects? var mapInformation: MapInformation? // 如果需要viewContext,也可以在这里添加对应的属性 var viewContext: NSManagedObjectContext? // 其他原有代码... }
- 在
MainApp的onAppear回调中完成注入:
@main struct MainApp: App { @UIApplicationDelegateAdaptor var appDelegate: AppDelegate @StateObject var environmentObjects = EnvironmentObjects() @StateObject var networkMonitor = NetworkMonitor() @StateObject var locationManager = LocationManager() @StateObject var mapInformation = MapInformation() @StateObject var popRoot = PopRoot() // 假设你使用CoreData,这里获取viewContext private let persistentContainer = NSPersistentContainer(name: "YourModelName") var body: some Scene { WindowGroup { ContentView() .environmentObject(environmentObjects) .environmentObject(mapInformation) .environment(\.managedObjectContext, persistentContainer.viewContext) // 注入其他环境对象 } .onAppear { // 初始化CoreData容器(如果需要) persistentContainer.loadPersistentStores { _, error in if let error = error { fatalError("CoreData load failed: \(error)") } } // 传递环境对象到AppDelegate appDelegate.environmentObjects = environmentObjects appDelegate.mapInformation = mapInformation appDelegate.viewContext = persistentContainer.viewContext } } }
- 修改
sessionExpired方法中的调用,确保使用注入后的对象:
func sessionExpired() { guard let envObjects = environmentObjects, let mapInfo = mapInformation, let context = viewContext else { print("Missing required objects for sign out") return } let alertController = UIAlertController(title: "Session Expired.", message: "Your login session has expired. Please sign in again.", preferredStyle: .alert) let okAction = UIAlertAction(title: "OK", style: .default) { action in FirebaseFunctions.signOutOfFirebase(saveAt: context, with: envObjects, with: mapInfo, deleteCoreData: true) } alertController.addAction(okAction) UIApplication.shared.windows.first { $0.isKeyWindow }?.rootViewController?.present(alertController, animated: true) }
方法2:将Firebase状态监听逻辑移到SwiftUI视图中
既然EnvironmentObject是为SwiftUI设计的,更合理的做法是把token检查逻辑放到SwiftUI的顶层视图中,直接访问环境对象:
struct ContentView: View { @EnvironmentObject var environmentObjects: EnvironmentObjects @EnvironmentObject var mapInformation: MapInformation @Environment(\.managedObjectContext) var viewContext var body: some View { // 你的视图内容 .onAppear { setupFirebaseAuthListener() } } private func setupFirebaseAuthListener() { Auth.auth().addStateDidChangeListener { auth, user in guard let user = user else { print("No user is signed in.") return } user.getIDTokenResult { result, error in if let error = error { print("Could not get the ID token result: \(error)") self.showSessionExpiredAlert() return } guard let expirationDate = result?.expirationDate else { print("Could not get the expiration date from the ID token result.") self.showSessionExpiredAlert() return } if Date() >= expirationDate { print("Login session has expired.") self.showSessionExpiredAlert() } } } } private func showSessionExpiredAlert() { let alertController = UIAlertController(title: "Session Expired.", message: "Your login session has expired. Please sign in again.", preferredStyle: .alert) let okAction = UIAlertAction(title: "OK", style: .default) { action in FirebaseFunctions.signOutOfFirebase(saveAt: self.viewContext, with: self.environmentObjects, with: self.mapInformation, deleteCoreData: true) } alertController.addAction(okAction) UIApplication.shared.windows.first { $0.isKeyWindow }?.rootViewController?.present(alertController, animated: true) } }
这种方式不需要修改AppDelegate,完全在SwiftUI环境中处理,避免了跨生命周期的状态访问问题。
内容的提问来源于stack exchange,提问作者Iraklis Eleftheriadis
相关产品推荐
相关产品推荐

