0

I try to create an instance of RootViewModel once by injecting it in the RootViewController when the app launches, in the didFinishLaunchingWithOptions of AppDelegate.swift so it doesn't get created multiple times.

Here's the code snippet:

...

var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        guard let rootViewController = window?.rootViewController as? RootViewController else {
            fatalError("Unable to Instantiate the root view controller")
        }

        let rootViewModel = RootViewModel()
        
        rootViewController.viewModel = rootViewModel
        
        return true
    }

...

The RootViewModel is a basic swift class with no implementation yet, and the RootViewController has an optional viewModel property to allow for the injection.

var viewModel: RootViewModel?

Here's my issue: Each time I run the app, it stops at the fatalError which I created to know if everything went well with the creation of the rootViewController. So, it means everything didn't go well.

I think the window property is still null at time of creating the rootViewController, but I am not sure how to resolve this.

I have tried creating the same thing in the SceneDelegate with no success.

What can I do to resolve this issue? I am using XCode Version 12.5

Waheed
  • 41
  • 2
  • 8
  • Why dont you just put your initialization of your RootViewModel into "viewDidLoad" of your RootViewController? – LukeSideWalker May 05 '21 at 08:33
  • Thanks for the suggestions @LukeSideWalker, however I think that would aside other things bring about a tight coupling between the RootViewController and RootViewModel. That's why I decided to go with Injection. – Waheed May 05 '21 at 08:49
  • 1
    Why don't you try to check in debugger what is inside of `window?.rootViewController`? Maybe you just put your ViewController into `UINavigationController` and that's why `rootVC` is not VC of your class – Alex Antonyuk May 05 '21 at 09:20
  • Hmm. @AlexAntonyuk when I checked ```window?.rootViewController```, it's **nil**. – Waheed May 05 '21 at 09:42
  • 1
    Since your RootViewController is obviously defined in InterfaceBuilder and not yet created at "AppDidLaunch" stage, you could use Notification in viewDidLoad (observed by your AppDelegate), that than injects. But that is not the best architecture. I dont use InterfaceBuilder, that's why I could create the VC in AppDelegate, assign to window.rootViewController and inject anything I want. But in your case, using IB, I can only think of the Notification way. – LukeSideWalker May 05 '21 at 09:53
  • 1
    If you want to use this approach you will need to add the code to create the `UIWindow` and instantiate the root view controller from your storyboard. The automatic creation of these items only occurs if `window` is `nil` after `didFinishLaunching` returns – Paulw11 May 05 '21 at 09:59
  • @LukeSideWalker I have tried creating the **rootViewController** using the *RootViewController* class and then assigning it to ```window?.rootViewController```. After which I created an instance of the **RootViewModel** and then injected it into the **viewModel** property of the **rootViewController**. I believe after the app launch, the viewModel should then be **MYAppName.RootViewModel** when I use ```print(viewModel ?? "Unable to create viewModel")``` to inspect it in the **viewDidLoad** of the **RootViewController**... ...but **Unable to create viewModel** is what I get! – Waheed May 05 '21 at 10:06
  • @Paulw11 could you expatiate on your suggestion because I think I am getting the error because the **window** property is actually **nil**. Thanks – Waheed May 05 '21 at 10:09
  • Do you have a scene delegate in your app? – Paulw11 May 05 '21 at 10:09
  • Yes @Paulw11 I do! – Waheed May 05 '21 at 10:15
  • Then you need to access the root view controller there, – Paulw11 May 05 '21 at 10:19

1 Answers1

1

Since you have adopted the new scene lifecycle and have a scene delegate, you need to access the root view controller in the willConnectTo scene delegate function.

This is because your app may no longer have a single window, but may have multiple windows if you, for example, support multiple views on iPadOS.


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).
     guard let scene = (scene as? UIWindowScene) else { return }
        
     if let rootVC = scene.windows.first?.rootViewController as? RootViewController {
         rootViewController.viewModel = RootViewModel()
    }
}
Paulw11
  • 108,386
  • 14
  • 159
  • 186
  • It worked. Thanks @Paulw11. Could you explain why this has to be done? Much appreciate bro! – Waheed May 05 '21 at 10:30
  • Think you already provided the reason: `Since you have adopted the new scene lifecycle and have a scene delegate, you need to access the root view controller in the willConnectTo scene delegate function` Thanks @Paulw11 – Waheed May 05 '21 at 10:34