71

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:

Cœur
  • 37,241
  • 25
  • 195
  • 267
Krunal
  • 77,632
  • 48
  • 245
  • 261
  • 2
    "does not work" is not a sufficient problem description. – rmaddy Sep 24 '19 at 15:57
  • 1
    What version of iOS are you testing this with? – rmaddy Sep 24 '19 at 15:57
  • 2
    Setting root viewcontrollers like this is over with iOS 12. You need to use SceneDelegate. – Shreeram Bhat Sep 24 '19 at 15:58
  • There is something odd with XCode 11 and window root. Second question I see today. https://stackoverflow.com/questions/58082743/set-rootviewcontroller-ios-13 – Marina Aguilar Sep 24 '19 at 15:59
  • @summerfinn3 There's nothing odd with Xcode 11. Things changed in iOS 13 and lots of people are not adjusting properly to iOS 13. – rmaddy Sep 24 '19 at 16:04
  • @rmaddy - Is it because of UIScene or SceneDelegate, a new concept added in iOS 13? – Krunal Sep 24 '19 at 16:09
  • @Krunal Probably. Under iOS 13, the window and root should be setup in the scene delegate, not the app delegate. But you still haven't defined what "does not work" means. – rmaddy Sep 24 '19 at 16:11
  • @rmaddy - I found following solution. It resolved my problem. But don't know, how and why? – Krunal Sep 24 '19 at 16:34
  • 1
    Read the [Scenes](https://developer.apple.com/documentation/uikit/app_and_environment/scenes) documentation and watch the relevant videos from WWDC 2019. – rmaddy Sep 24 '19 at 16:37
  • 1
    For anyone looking for an updated "Single View App" Xcode template that supports iOS 12 and 13 as well as supporting either a storyboard or an all-code user interface, see https://github.com/rmaddy/XcodeTemplates. – rmaddy Sep 25 '19 at 05:32

16 Answers16

138

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

Carlos García
  • 1,719
  • 1
  • 13
  • 6
  • 4
    good solution if you don't plan on using SwiftUI or else the SceneDelegate.swift file just takes up unnecessary space. – grantespo Apr 16 '20 at 15:16
  • 2
    This is indeed the best solution! – Niraj Jul 02 '20 at 09:15
  • This crashes my app saying: 'Application windows are expected to have a root view controller at the end of application launch' – S.S.D May 19 '21 at 06:39
  • Wow, took me 2 days to find this solution. Thank you! – Charlie Jun 24 '21 at 16:24
  • damn never mind this caused my application to crash and say this [SceneConfiguration] Info.plist contained no UIScene configuration dictionary (looking for configuration named "(no name)") – Edison Espinosa Feb 18 '23 at 23:13
60

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:

Krunal
  • 77,632
  • 48
  • 245
  • 261
  • how to change RootViewController from any UIViewController (e.g. HomeVC/LoginVC)? – Jay Patel Oct 08 '19 at 19:30
  • @JD. If you are trying with iOS 13, first get Window scene object with the help of SceneDelegate, then window object (use the source code similar to above one answered) and then your regular code for setup of navigation controller. – Krunal Oct 09 '19 at 04:26
  • 1
    How to get windowScene instance? I am trying to access "window" on SceneDelegate.swift. but not getting. – Jay Patel Oct 09 '19 at 11:03
  • Use of unresolved identifier 'AppConstants'. Where is AppConstant? – Rubaiyat Jahan Mumu Nov 12 '19 at 17:38
  • @mumu - Thank you for highlighting this issue. I've corrected it now. – Krunal Nov 13 '19 at 10:00
  • This creates an issue with text fields. When using this solution, any text fields in the view you set as your new initial controller will print the error "Connection to daemon was invalidated" – msweet168 May 06 '20 at 02:58
31

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()
     }
shim
  • 9,289
  • 12
  • 69
  • 108
Vikram Chaudhary
  • 580
  • 4
  • 14
13

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
}
Renan Lopes
  • 1,229
  • 2
  • 10
  • 17
Gurpreet Singh
  • 803
  • 9
  • 16
12

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.

Hemant Bavle
  • 3,297
  • 1
  • 24
  • 30
6

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()
    }
WINSergey
  • 1,977
  • 27
  • 39
Utsav Dave
  • 85
  • 1
  • 3
  • 9
5

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

Amit Thakur
  • 1,081
  • 12
  • 14
5

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

Pavel Bogart
  • 405
  • 5
  • 16
4
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)
              }
    }
Aditya Ahuja
  • 316
  • 3
  • 17
2
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       

}
David Buck
  • 3,752
  • 35
  • 31
  • 35
Razi
  • 141
  • 2
  • 6
  • 1
    Welcome to Stack Overflow. Please consider providing some explanation/commentary to accompany your code so that a reader can understand what you have done. – Alan May 04 '20 at 07:35
2

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

Naishta
  • 11,885
  • 4
  • 72
  • 54
1

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

Bruno Muniz
  • 316
  • 3
  • 17
  • I did something along these lines as well, except whatever I did included unchecking the "Supports multiple windows" setting in the application target's "General" settings. And I feel like that was part of what resolved the issue for me. – shoe Jun 22 '22 at 21:41
1
    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()
eng mohamed emam
  • 549
  • 6
  • 13
1
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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
sudayn
  • 1,169
  • 11
  • 14
1

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() 
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
-1

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.

unferna
  • 105
  • 1
  • 2