iOS 13: Swift - 'Set application root view controller programmatically' does not work

IosSwiftAppdelegateIos13Rootviewcontroller

Ios Problem Overview


I have following code in my AppDelegate.swift to setup root view controller for an iOS application. But it does not work. It follows Target structure (defined under General tab) and ignores this code.

(Xcode 11, Swift 5.1, iOS 13)

class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        window = UIWindow(frame: UIScreen.main.bounds)
        guard let rootVC = UIViewController() else {
            print("Root VC not found")
            return true
        }
        let rootNC = UINavigationController(rootViewController: rootVC)
        window?.rootViewController = rootNC
        window?.makeKeyAndVisible()
  
        return true
    }
}

Unable to understand where is the issue.

I tried following references also but no luck:

Ios Solutions


Solution 1 - Ios

To choose a previous approach to the one supported by SwiftUI, from a project created in Xcode 11, you can follow these steps.

Steps for get old aproach

Solution 2 - Ios

I tried following two options and both of these working for me. With iOS-13 (Xcode 11) a new file SceneDelegate.swift with the concept of UIWindowScene is enabled by default.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        
        guard let windowScene = (scene as? UIWindowScene) else { return }
        

        self.window = UIWindow(windowScene: windowScene)
        //self.window =  UIWindow(frame: UIScreen.main.bounds)

        let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
        guard let rootVC = storyboard?.instantiateViewController(identifier: "ViewControllerIdentifierName") as? ViewController else {
            print("ViewController not found")
            return
        }
        let rootNC = UINavigationController(rootViewController: rootVC)
        self.window?.rootViewController = rootNC
        self.window?.makeKeyAndVisible()
    }
}

Alternate:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        let windowScene = UIWindowScene(session: session, connectionOptions: connectionOptions)
        self.window = UIWindow(windowScene: windowScene)
        //self.window =  UIWindow(frame: UIScreen.main.bounds)
        let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
        guard let rootVC = storyboard?.instantiateViewController(identifier: "ViewControllerIdentifierName") as? ViewController else {
            print("ViewController not found")
            return
        }
        let rootNC = UINavigationController(rootViewController: rootVC)
        self.window?.rootViewController = rootNC
        self.window?.makeKeyAndVisible()
        
    }
}

I don't know, why and how it works but it resolved my problem.

Reference docs that helped me:

Solution 3 - Ios

I tried the following approach and it's working for me in iOS 13 and also tested on iOS 12.4.2 from Xcode 11.

func resetRoot() {
            guard let rootVC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController") as? ViewController else {
                return
            }
            let navigationController = UINavigationController(rootViewController: rootVC)
            
            UIApplication.shared.windows.first?.rootViewController = navigationController
            UIApplication.shared.windows.first?.makeKeyAndVisible()
     }

Solution 4 - Ios

Here's what work for both iOS 13.x and iOS 12.x and below

For iOS 13,In the Scene Delegate

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
            guard let windowScene = (scene as? UIWindowScene) else { return }
            self.window = UIWindow(frame: windowScene.coordinateSpace.bounds)
           //Make sure to do this else you won't get 
           //the windowScene object using UIApplication.shared.connectedScenes
            self.window?.windowScene = windowScene 
            let storyBoard: UIStoryboard = UIStoryboard(name: storyBoardName, bundle: nil)
            window?.rootViewController = storyBoard.instantiateInitialViewController()
            window?.makeKeyAndVisible()
        }

In a utility class, I wrote below function to get the window object and assign it to the appdelegate.window. According to my needs, I needed to set root view controller at multiple places in different scenarios for which I needed the window object.

static func redirectToMainNavRVC(currentVC: UIViewController){
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        let vc = UIStoryboard(name: appDelegate.storyBoardName, bundle: nil).instantiateViewController(withIdentifier: "MainNavigationViewController") as! MainNavigationViewController
    if #available(iOS 13.0, *){
        if let scene = UIApplication.shared.connectedScenes.first{
            guard let windowScene = (scene as? UIWindowScene) else { return }
            print(">>> windowScene: \(windowScene)")
            let window: UIWindow = UIWindow(frame: windowScene.coordinateSpace.bounds)
            window.windowScene = windowScene //Make sure to do this
            window.rootViewController = vc
            window.makeKeyAndVisible()
            appDelegate.window = window
        }
    } else {
        appDelegate.window?.rootViewController = vc
        appDelegate.window?.makeKeyAndVisible()
    }
}

This worked well for me. Hopefully, it works for others too.

Solution 5 - Ios

First Case

If major of your project is build in storyboard and before Xcode 11 is used for development and you are not using SwiftUI you want to use your old classes associated with AppDelegate.

  • Then try to remove "Application Scene Manifest" in info.pllist.

  • Remove ScenceDelegate from project completely.

    Then you will able to use your old code.

Second Case

If you want to use both Appdelegte and ScenceDelegate then below is working code.

App Delegate Code:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {   
 if #available(iOS 13.0, *){
    //do nothing we will have a code in SceneceDelegate for this 
} else {
    let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
    let VC = mainStoryboard.instantiateViewController(withIdentifier: "LoginVC") as! LoginVC
    navigationController?.isNavigationBarHidden = true
    navigationController = UINavigationController(rootViewController: VC)
    navigationController?.isNavigationBarHidden = true // or not, your choice.
    self.window = UIWindow(frame: UIScreen.main.bounds)
    self.window!.rootViewController = navigationController
}
return true
}

ScenceDelegate Code:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

    let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
    let VC = mainStoryboard.instantiateViewController(withIdentifier: "LoginVC") as! LoginVC
    navigationController?.isNavigationBarHidden = true
    guard let windowScene = (scene as? UIWindowScene) else { return }
    self.window = UIWindow(frame: windowScene.coordinateSpace.bounds)
    window.windowScene = windowScene
    window.rootViewController = VC
    window.makeKeyAndVisible()
    let appDelegate = UIapplication.shared.delegate as! AppDelegate
    appDelegate.window = window
}

Solution 6 - Ios

If you follow this answer https://stackoverflow.com/a/58084612/6667477

and if you initialise a rootController programmatically you also have to remove reference to main storyboard from:

  1. Info.plist.

enter image description here

  1. Deployment info

enter image description here

Solution 7 - Ios

var window: UIWindow?

has been moved from AppdDelegate.swift to SceneDelegate.swift.

So I used rootViewController in the Scene Delegate class and it works -

   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 as? UIWindowScene) else { return }
        
        if let tabBarController = window?.rootViewController as? UITabBarController {
                  let storyboard = UIStoryboard(name: "Main", bundle: nil)
                  let vc = storyboard.instantiateViewController(identifier: "NavController")
                  
                  vc.tabBarItem = UITabBarItem(tabBarSystemItem: .topRated, tag: 1)
                  tabBarController.viewControllers?.append(vc)
              }
    }

Solution 8 - Ios

For Xcode 11+ and Swift 5+ inside SceneDelegate.swift


var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }
        let window = UIWindow(windowScene: windowScene)
        let submodules = (
            home: HomeRouter.createModule(),
            search: SearchRouter.createModule(),
            exoplanets: ExoplanetsRouter.createModule()
        )
            
        let tabBarController = TabBarModuleBuilder.build(usingSubmodules: submodules)
            
        window.rootViewController = tabBarController
        self.window = window
        window.makeKeyAndVisible()
    }

Solution 9 - Ios

If you don't want to use main storyboard, if you want to create you own views programmatically.

For xcode 11.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

    guard let windowScene = (scene as? UIWindowScene) else { return }
    window = UIWindow(windowScene: windowScene)
    window?.makeKeyAndVisible()
    window?.rootViewController = UINavigationController(rootViewController:      ViewController())
}

This will defenetily work. Thanks

Solution 10 - Ios

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
         
    guard let windowScene = (scene as? UIWindowScene) else { return }
    self.window = UIWindow(windowScene: windowScene)               
      self.window?.makeKeyAndVisible()
    
    let layout = UICollectionViewFlowLayout()
    let swipingController = SwipingController(collectionViewLayout: layout)
          self.window?.rootViewController = swipingController       
          
}
          

Solution 11 - Ios

I just deleted a row on the info.plist that says something about Application scene. Works for me.

Solution 12 - Ios

    let MainViewController  = mainStoryboard.instantiateViewController(withIdentifier: "MainViewController") as! MainViewController

                    let nvc:rootNavigation  = mainStoryboard.instantiateViewController(withIdentifier: "rootNavigation") as! rootNavigation
                    nvc.viewControllers = [Mainmap]
                   
                    leftViewController.mainViewController = nvc

UIApplication.shared.windows.first?.backgroundColor = UIColor(red: 236.0, green: 238.0, blue: 241.0, alpha: 1.0)
                    UIApplication.shared.windows.first?.rootViewController = nvc
                    UIApplication.shared.windows.first?.makeKeyAndVisible()

Solution 13 - Ios

let controller = ....
let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
keyWindow?.rootViewController = controller
keyWindow?.makeKeyAndVisible()

These are the basic code required to set root view controller.

Solution 14 - Ios

Important Points to Note: If you plan to add SwiftUI to existing project, then the project's deployment target must be set to iOS 13, and then the project must have SceneDelegate. You cannot launch the app with just AppDelegate. (If you are not intending to use SwiftUI, but still need min deployment target to be iOS 13, follow Carlos García's answer above)

If you are trying to add code in SwiftUI, in an already created project , make sure

  • Minimum deployment Target is set to iOS 13
  • You have both Appdelegate + SceneDelegate setup correctly
  • Mainly, have the Application Scene Manifest entry in Info.plist, possibly create a dummy Project and copy entry from there

enter image description here

enter image description here

enter image description here

Solution 15 - Ios

This will help you to set your root view controller through scene delegate.

let navVc = UINavigationController(rootViewController: UIViewController)
navVc.setNavigationBarHidden(true, animated: false )
            
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let sceneDelegate = windowScene.delegate as? SceneDelegate  else { return } 
            
sceneDelegate.window?.rootViewController = navVc
sceneDelegate.window?.makeKeyAndVisible() 

Solution 16 - Ios

I did two things. Firstly I set a Notification into SceneDelegate, then when I needed to change the RootViewController I did a Notification Post and it worked. But it's an anwful solution.

After that

My boss recommended me to change the Controllers of the NavigationController, something like this:

func logged() {
    let mainStoryboard: UIStoryboard = UIStoryboard(name: "MainTabViewController", bundle: nil)
    let mainVC = mainStoryboard.instantiateInitialViewController()
    
    self.navigationController?.setViewControllers([mainVC!], animated: false)
}

I know that perhaps it's not the best solution, but I see it cleaner.
I'm working now on iOS 13 and I didn't want to use deprecated things.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKrunalView Question on Stackoverflow
Solution 1 - IosCarlos GarcíaView Answer on Stackoverflow
Solution 2 - IosKrunalView Answer on Stackoverflow
Solution 3 - IosVikram ChaudharyView Answer on Stackoverflow
Solution 4 - IosHemant BavleView Answer on Stackoverflow
Solution 5 - IosGurpreet SinghView Answer on Stackoverflow
Solution 6 - IosPavel BogartView Answer on Stackoverflow
Solution 7 - IosAditya AhujaView Answer on Stackoverflow
Solution 8 - IosUtsav DaveView Answer on Stackoverflow
Solution 9 - IosAmit ThakurView Answer on Stackoverflow
Solution 10 - IosRaziView Answer on Stackoverflow
Solution 11 - IosBruno MunizView Answer on Stackoverflow
Solution 12 - Ioseng mohamed emamView Answer on Stackoverflow
Solution 13 - IossudaynView Answer on Stackoverflow
Solution 14 - IosNaishtaView Answer on Stackoverflow
Solution 15 - IosAdeesh PatelView Answer on Stackoverflow
Solution 16 - IosunfernaView Answer on Stackoverflow