10

In SwiftUI 5.1, I want to use AppDelegate to create an userData object. userData will also contain BLE advertisement data, which will be updated from AppDelegate, too. These data should be available to the UI to display those values.

In AppDelegate I use

@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { private var centralManager : CBCentralManager! var userData: UserData! func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. userData = UserData() return true } 

In SceneDelegate I want to pass to the view using

class SceneDelegate: UIResponder, UIWindowSceneDelegate { @EnvironmentObject var userData: UserData var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). // Use a UIHostingController as window root view controller if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = UIHostingController(rootView: BeaconList().environmentObject(userData) ) self.window = window window.makeKeyAndVisible() } } 

Compiling works fine, but when running the code I get

Thread 1: Fatal error: Reading EnvironmentObject outside View.body

If I remove the

.environmentObject(userData)

I get

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

Essentially, I'm trying to make create and update the userData object from AppDelegate, and to display from SceneDelegate and view below.

How can I implement this?

3
  • Why can't you just use SceneDelegate? This is what Apple is requiring for this sort of thing. (You may have found an outlier case that isn't covered, but I'm doubtful.) Commented Jul 17, 2019 at 17:37
  • @dfd wouldn't that create a new instance of userData each time a new scene is created? Commented Aug 19, 2020 at 6:15
  • @PeterSchorn you may be right. My comment looks to be back around SwiftUI 1.0 beta 3 time, which is waaayyyy back. :-) I'm working on a UIKit app right now, and it's for iPad only, where I'm working through supporting multiple instances (or scenes) and eventually drag & drop. Looking at my comment above, I'm still standing by it's intent - in most cases, if not all, Apple wants things sandboxed by scene or instance of an app, not all instances of it. Commented Aug 19, 2020 at 8:43

2 Answers 2

7

Maybe you can use the UIApplication.shared.delegate to get the AppDelegate an access the user data:

class AppDelegate: UIResponder, UIApplicationDelegate { var userData: UserData! func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. userData = UserData() return true } } 
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). let userData = (UIApplication.shared.delegate as! AppDelegate).userData // Use a UIHostingController as window root view controller if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(userData)) self.window = window window.makeKeyAndVisible() } } 
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, I followed this way with the AppDelegate, works fine.
this is an awful way of violating solid principles and making appdelegate a global singleton. Unfortunately I don't have a better suggestion - apple forces our hand to have poor design because it won't give us any ability to properly inject dependencies into scene delegates.
1

You can inject using the userInfo on UISceneSession.

However, this won't get called on every app session. If iOS is able to cache your session then in the next app start it will not pass through the app delegate. So you will need to take proper care to cache whatever information you are passing from the App Delegate to the Scene Delegate yourself.

// AppDelegate.swift var client: Client func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. client = Client() return true } func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. connectingSceneSession.userInfo = ["client": client] let config = UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) config.delegateClass = SceneDelegate.self config.sceneClass = UIWindowScene.self return config } 
// SceneDelegate.swift func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { if let client = session.userInfo?["client"] as? Client { ... } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.