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

16

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:

Op answered 24/9, 2019 at 15:55 Comment(10)
"does not work" is not a sufficient problem description.Toms
What version of iOS are you testing this with?Toms
Setting root viewcontrollers like this is over with iOS 12. You need to use SceneDelegate.Monition
There is something odd with XCode 11 and window root. Second question I see today. #58083243Paez
@summerfinn3 There's nothing odd with Xcode 11. Things changed in iOS 13 and lots of people are not adjusting properly to iOS 13.Toms
@Toms - Is it because of UIScene or SceneDelegate, a new concept added in iOS 13?Op
@Op 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.Toms
@Toms - I found following solution. It resolved my problem. But don't know, how and why?Op
Read the Scenes documentation and watch the relevant videos from WWDC 2019.Toms
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 github.com/rmaddy/XcodeTemplates.Toms
P
139

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

Pacify answered 20/2, 2020 at 22:19 Comment(5)
good solution if you don't plan on using SwiftUI or else the SceneDelegate.swift file just takes up unnecessary space.Thanh
This is indeed the best solution!Seldon
This crashes my app saying: 'Application windows are expected to have a root view controller at the end of application launch'Welter
Wow, took me 2 days to find this solution. Thank you!Pearlypearman
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)")Ephrem
O
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:

Op answered 24/9, 2019 at 16:29 Comment(6)
how to change RootViewController from any UIViewController (e.g. HomeVC/LoginVC)?Belorussia
@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.Op
How to get windowScene instance? I am trying to access "window" on SceneDelegate.swift. but not getting.Belorussia
Use of unresolved identifier 'AppConstants'. Where is AppConstant?Polacre
@mumu - Thank you for highlighting this issue. I've corrected it now.Op
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"Thinnish
L
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()
     }
Lidialidice answered 10/10, 2019 at 9:13 Comment(6)
This is showing warning for deprecated of keyWindow. Another option is "UIApplication.shared.windows.first?.rootViewController"Belorussia
Nope. The windows count is zero. To make it work, I had to create an instance UIWindow() and assign it to delegate's window prop, then it started working for 12.x versions.Palgrave
@HemantBavle Can you detail how you made this work, and does it work with both ios 12 & 13 ?Hengel
@Hengel I just added above function in AppDelegate and using the function by instance of AppDelegate. it's working fine in iOS 12 & 13 bothLidialidice
Excellent answer its working 100% thanks you have saved my half dayBaylor
This is working but I after. implement this I can see two navigation barScrutiny
D
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
}
Dybbuk answered 9/12, 2019 at 7:4 Comment(0)
P
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.

Palgrave answered 1/11, 2019 at 6:5 Comment(0)
R
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()
    }
Ratiocinate answered 17/7, 2020 at 5:43 Comment(0)
T
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

Thomasinethomason answered 13/1, 2020 at 9:13 Comment(3)
Totally identical to existing answer https://mcmap.net/q/274223/-ios-13-swift-39-set-application-root-view-controller-programmatically-39-does-not-workLitton
@matt. Agreed! but number of lines decreases. Thanks for your suggestion.Thomasinethomason
This is better and cleaner if you don't use a storyboard.Bodoni
T
5

If you follow this answer https://mcmap.net/q/274223/-ios-13-swift-39-set-application-root-view-controller-programmatically-39-does-not-work

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

Toehold answered 24/10, 2020 at 19:46 Comment(0)
I
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)
              }
    }
Ingrain answered 17/12, 2019 at 10:11 Comment(0)
P
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       

}
Protect answered 4/5, 2020 at 1:40 Comment(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.Aric
A
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

Antung answered 26/8, 2021 at 6:30 Comment(0)
H
1

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

Hoahoactzin answered 2/5, 2020 at 21:24 Comment(1)
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.Roseboro
N
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()
Necessarily answered 28/8, 2020 at 5:59 Comment(0)
N
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.

Naman answered 28/8, 2020 at 7:43 Comment(0)
T
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() 
Triumvirate answered 31/5, 2022 at 6:39 Comment(0)
S
-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.

Sulphurbottom answered 14/12, 2019 at 12:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.