How Get Rootviewcontroller with iPados Multi Window (Scenedelegate)

How to modally present a ViewController from SceneDelegate?

You can't present a UIViewController (modally or fullscreen) which is window's rootViewController.

To show Onboarding screen when user open app for the first time, set Home Screen/Login Screen (or any screen you want) as rootViewController in SceneDelegate. Then from that UIViewController you can check if user open app for first time or not. Depending on that you can show the Onboarding screen.

You must embed the rootViewController in a UINavigationViewController to present/push another UIViewController

In SceneDelegate.swift modify the code like below.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: scene)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
//embed in UINavigationController
let navController = UINavigationController(rootViewController: controller)
window?.rootViewController = navController
window?.makeKeyAndVisible()
}

Then in the Home screen (i assume it as HomeViewController) check the status of first opening and present Onboarding screen.

class HomeViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "OnboardingViewController") as! OnboardingViewController
controller.modalPresentationStyle = .formSheet
self.present(controller, animated: true)
}
}

You can get the rootViewController from anywhere in the app by using the code below. And then present any ViewController you want to show.

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "OnboardingViewController") as! OnboardingViewController
controller.modalPresentationStyle = .formSheet
let rootVC = UIApplication.shared.windows.first!.rootViewController
rootVC?.present(controller, animated: true)

How to use old method of setting rootViewController in AppDelegate using Swift

Follow these steps to use AppDelegate and opt-out for SceneDelegate

  1. Go to Info.plist and remove Application Scene Manifest entry from Info.plist.
  2. Remove SceneDelegate.swift
  3. Add var window: UIWindow? in your AppDelegate.swift file
  4. Delete the UISceneSession Lifecycle code from AppDelegate.swift

    import UIKit

    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {

    var window : UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    let mainStoryBoard = UIStoryboard(name: "Main", bundle: Bundle.main)
    let loginController = mainStoryBoard.instantiateViewController(withIdentifier: "HomeViewController")
    self.window?.rootViewController = loginController
    self.window?.makeKeyAndVisible()
    return true
    }
    }

Make sure to give "HomeViewController" storyboardID to your view controller.

This is how your AppDelegate.swift file should look now. You are good to go!

Swift/iOS13 - Access SceneDelegate window variable in a ViewController

Updated

From iOS 13, apps can have multiple active windows, so you need to access the window you want. So you can access a window of any View like this:

self.view.window

if you really want to access the UISceneDelegate you can access it like:

self.view.window.windowScene.delegate

Old: and NOT recommended:

Assuming

  1. there is only one scene delegate.
  2. There is only one scene and one window.
  3. All view controllers in the app are all part of that one scene and its window.

You can implement a helper variable in SceneDelegate like this:

private(set) static var shared: SceneDelegate?

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

guard let _ = (scene as? UIWindowScene) else { return }
Self.shared = self
}

then you can access it anywhere like this:

SceneDelegate.shared?.window // or anything else

Update root view controller after user login + iOS 13 and later

This is how I managed navigation for both the older version and the new version. So when the user has the latest iOS we need to setup root from sceneDelegate and for older version we need to setup root from appDelegate

AppDelegate.swift

class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 13, *) {

} else {
setupRoot()
}
return true
}

// MARK: UISceneSession Lifecycle
@available(iOS 13.0, *)
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

@available(iOS 13.0, *)
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {

}

func setupRoot() {
//Setup Your Root Here
//window?.rootViewController = objNavigationVC
//window?.makeKeyAndVisible()
}
}

SceneDelegate.swift

@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window = window
appDelegate.setupRoot()
}
}

Setting Up CoreData with SceneDelegate - unknown identifier 'window' error - iOS 13 onwards

The issue boils down to the changes, primarily, support for multiple scenes in iOS 13 and above. check this reddit link for the discussion.

The solution is to move some of things from AppDelegate to SceneDelegate.

Here is the final form of the essential parts of the above two classes.

----SceneDelegate

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: windowScene)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let rootVC = storyboard.instantiateViewController(identifier: "ViewController") as? ViewController else {
print("ViewController not found")
return
}
//set the storage here
rootVC.container = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer
//I dont want a UI navigation controller.
//let rootNC = UINavigationController(rootViewController: rootVC)
//self.window?.rootViewController = rootNC
//I want to use my basic view controller here. use rootNC to get a UI navigation controller
self.window?.rootViewController = rootVC
self.window?.makeKeyAndVisible()

}

--AppDelegate (remains unchanged )

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
//before iOS 13 you would be putting stuff here but not anymore.
return true
}

Finally, you will leave the storage related code, where it was, in AppDelegate itself.



Related Topics



Leave a reply



Submit