Black screen after adding SceneDelegate and updating Info.plist - ios

I am currently getting a blank screen with Xcode 11, Target iOS 13.0 (the app works fine with all below versions iOS 12.1 till 12.4), I want to make my App work for both iOS users above 12.1 and also 13.0 currently getting blank screen despite adding the below UIWindowSceneDelegate to my existing project and App Manifest:
adding App Manifest file
import UIKit
import SwiftUI
#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 user = UserDefaults.standard.object(forKey: "defaultsuserid")
let userSelfIdent = UserDefaults.standard.object(forKey: "userinitialident")
if let windowScene = scene as? UIWindowScene {
let internalWindow = UIWindow(windowScene: windowScene)
if (user != nil && userSelfIdent != nil){
let mainstoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewcontroller:UIViewController = mainstoryboard.instantiateViewController(withIdentifier: "swrevealviewcontroller") as! SWRevealViewController
internalWindow.rootViewController = newViewcontroller
self.window = internalWindow
internalWindow.makeKeyAndVisible()
}else {
guard let _ = (scene as? UIWindowScene) else { return }
}
}
}
The following is my AppDelegate which is getting called first and executing the didFinishLaunchWithOptions method. I want to know how can i make this method call only if my Target ios is less than 13.0 and call SceneDelegate method to initialize my rootViewController after 13.0?
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
#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>) {
}
#available(iOS 13.0, *)
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
if (user != nil && userSelfIdent != nil){
let mainstoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewcontroller:UIViewController = mainstoryboard.instantiateViewController(withIdentifier: "swrevealviewcontroller") as! SWRevealViewController
self.window?.rootViewController = newViewcontroller
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
Thread.sleep(forTimeInterval: 3.0)
UINavigationBar.appearance().barTintColor = UIColor(red:0.08, green:0.23, blue:0.62, alpha:1.0)
if (user != nil && userSelfIdent != nil){
let mainstoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewcontroller:UIViewController = mainstoryboard.instantiateViewController(withIdentifier: "swrevealviewcontroller") as! SWRevealViewController
self.window?.rootViewController = newViewcontroller
}
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let defaultUserID = UserDefaults.standard.string(forKey: "defaultUserID")
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
switch (application.applicationState) {
case UIApplicationState.active:
do something
case UIApplicationState.background, UIApplicationState.inactive:
let mainstoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewcontroller = mainstoryboard.instantiateViewController(withIdentifier: "swrevealviewcontroller") as! SWRevealViewController
self.window?.rootViewController = newViewcontroller
}
}

You have several issues here. It's important to read the documentation related to the app lifecycle which states what is called under iOS 13 and what is called under iOS 12.
You may also want to see my Single View App template that supports iOS 12 and 13.
Looking at your code, here is a summary of the problems:
AppDelegate:
You should only setup the main window and the root view controller if the app is being run under iOS 12 or earlier. You need to check this at runtime.
The func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) method should not be in the app delegate.
Not directly related but never sleep on app startup. Remove the Thread.sleep(forTimeInterval: 3.0) line. Users want to use your app, not stare at the launch screen longer than necessary. And blocking the main thread on app launch can cause your app to be killed.
SceneDelegate:
This is mostly fine but there is no reason for the guard let _ = (scene as? UIWindowScene) else { return } line, especially since it is inside an if let that already does that check.
You don't appear to be using SwiftUI so remove that import.
I would update your app delegate to be more like this:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
UINavigationBar.appearance().barTintColor = UIColor(red:0.08, green:0.23, blue:0.62, alpha:1.0)
if #available(iOS 13.0, *) {
// In iOS 13 setup is done in SceneDelegate
} else {
let window = UIWindow(frame: UIScreen.main.bounds)
self.window = window
if (user != nil && userSelfIdent != nil){
let mainstoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewcontroller:UIViewController = mainstoryboard.instantiateViewController(withIdentifier: "swrevealviewcontroller") as! SWRevealViewController
window.rootViewController = newViewcontroller
}
}
return true
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 13.0, *) {
// In iOS 13 setup is done in SceneDelegate
} else {
self.window?.makeKeyAndVisible()
}
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Not called under iOS 13 - See SceneDelegate sceneWillResignActive
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Not called under iOS 13 - See SceneDelegate sceneDidEnterBackground
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Not called under iOS 13 - See SceneDelegate sceneWillEnterForeground
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Not called under iOS 13 - See SceneDelegate sceneDidBecomeActive
}
// MARK: UISceneSession Lifecycle
#available(iOS 13.0, *)
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
#available(iOS 13.0, *)
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
Your scene delegate could be like:
#available(iOS 13.0, *)
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 }
let window = UIWindow(windowScene: windowScene)
self.window = window
if (user != nil && userSelfIdent != nil){
let mainstoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewcontroller:UIViewController = mainstoryboard.instantiateViewController(withIdentifier: "swrevealviewcontroller") as! SWRevealViewController
window.rootViewController = newViewcontroller
window.makeKeyAndVisible()
}
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Not called under iOS 12 - See AppDelegate applicationDidBecomeActive
}
func sceneWillResignActive(_ scene: UIScene) {
// Not called under iOS 12 - See AppDelegate applicationWillResignActive
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Not called under iOS 12 - See AppDelegate applicationWillEnterForeground
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Not called under iOS 12 - See AppDelegate applicationDidEnterBackground
}
}

Steps for running with iOS 13 and lower versions:
Change deployment target to iOS 12.
Replace the AppDelegate's methods with what they ought to have for iOS 12 development. Also add this:
var window: UIWindow?
Remove SceneDelegate.
Remove Application Scene Manifest in your info.plist.
It will work on both iOS 13 and lower iOS Version

Easily follow these steps:
Remove scene delegate file
Add below code to AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
}
Remove the Application Scene Manifest line from your .plist file

The accepted answer is correct in a lot of ways, but I believe it's not a complete answer. I posted a comment that already helped several people to fully solve the issue, so I will post this as an answer as well:
If you messed this up once and it's already installed in the simulator, every new launch will still have this issue, even if your settings are correct...
The solution is to remove the App and reinstall it with the corrected code! It's a crazy fix, but it works!

I commented 2 scene methods in the AppDelegate, and then it works
please check my screenshot

I was stuck with this problem, and finally i solved removing the searchDisplayController references from storyboard.
<searchDisplayController id="pWz-So-g6H">
<connections>
<outlet property="delegate" destination="Yci-sd-Mof" id="fjs-ah-jLs"/>
<outlet property="searchContentsController" destination="Yci-sd-Mof" id="gQN-1r-gti"/>
<outlet property="searchResultsDataSource" destination="Yci-sd-Mof" id="2Jf-lh-Ute"/>
<outlet property="searchResultsDelegate" destination="Yci-sd-Mof" id="Hap-SA-f02"/>
</connections>
</searchDisplayController>

When I had a similar issue it was due to the Single-App template generated using Xcode 11.0 was incompatible to the one being needed for an app built with Xcode 11.2.
So I just created a new Single-Page-App with Xcode 11.2 and copied the generated SceneDelegate to my old project which was created using Xcode 11.0.
After that, the blank screen was gone an my interface visible once more.

Please refer above image and add the line in AppDelegate.

xcode 11.6
#ionic/react capacitor app
UPDATED:7/22/2020
We had black screen issue at TestFlight too.
WE finally solved.
Check config.capacitor.json and see if u have localhost there,
delete URLs under server section..
we(I) forgot our URL localhost still there when we run release, to be honest i did not know that could be problem or never think to check there,
I spent time(days) to check storyBoard suggestions.

I was porting some sample code from older App Delegate presentation to a new Xcode project with the new Scene Delegate infrastructure.
Even after moving the code the Scene Delegate, I was getting a black screen.
The issue was the way in which I was getting a reference to the window.
Use the new UIWindow(windowScene:) initializer rather than initWithFrame:.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// OLD: causes black screen
self.window = UIWindow(frame: UIScreen.main.bounds)
// NEW: works perfectly
guard let windowScene = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: windowScene)
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "INITAL_VIEW_CONTROLLER")
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
}
}

Related

Black screen on Launch: How to support all versions iOS 10 and 13 above in App and Scene Delegate

My app supports older versions of iOS 10.0 and above. When I try to launch my app in iOS 13, it shows a black window. It does configure initial ViewController and shows labels when I remove the scene delegate. Everything works in iOS 12 and under with just App delegate.
In my App delegate:
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
configureInitialViewController()
return true
}
func configureInitialViewController()
{
var initialVC: UIViewController
let mainsb = UIStoryboard(name: "Main", bundle: nil)
if Auth.auth().currentUser != nil {
//print("successfully signed in")
initialVC = mainsb.instantiateViewController(withIdentifier: IDENTIFIER_MAIN_TABBAR)
} else {
//print("successfully signed out")
initialVC = mainsb.instantiateViewController(withIdentifier: IDENTIFIER_WELCOME_NAV)
}
window?.rootViewController = initialVC
window?.makeKeyAndVisible()
}
func applicationWillResignActive(_ application: UIApplication) {
Api.User.isOnline(bool: false)
}
// MARK: UISceneSession Lifecycle
#available(iOS 13.0, *)
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
#available(iOS 13.0, *)
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
In my Scene delegate:
#available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
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 }
}
func sceneWillResignActive(_ scene: UIScene) {
Api.User.isOnline(bool: false)
}
}
I also deleted Application Scene Manifest in my info.plist.
Can someone help me check if I'm setting up version supports correctly?
I would really appreciate any help.
Update:
I tested it works in iOS 13.5 but not 13.2 or 14.3. Why is it showing different behaviors in different versions?
In your AppDelegate inside didFinishLaunchingWithOptions add this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
if #available(iOS 13.0, *){}
else{
//Load your vc
}
return true
}
Then in your SceneDelegate inside willConnectTo add these:
#available(iOS 13.0, *)
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 }
guard let windowScene = (scene as? UIWindowScene) else {return}
let window:UIWindow = UIWindow(windowScene: windowScene)
self.window = window
self.window?.rootViewController = // set you VC
self.window?.makeKeyAndVisible()
}

iOS Swift Firebase Checking user at app startup not working

I developed an app with firebase. The application checks to see if any users are logged in every boot in sceneDelegate.swift file. But its works only iOS 13 devices. iOS 12 devices users has to log in every time. How can I fix this problem? Thanks.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
#available(iOS 13.0, *)
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).
let currentUser = Auth.auth().currentUser
if currentUser != nil {
let board = UIStoryboard(name: "Main", bundle: nil)
let navigationController = board.instantiateViewController(identifier: "navigationController") as! UINavigationController
window?.rootViewController = navigationController
}
guard let _ = (scene as? UIWindowScene) else { return }
}
SceneDelegate was introduced in iOS 13. For iOS 12 and below you should implement this same logic in the AppDelegate's didFinishLaunchingWithOptions method like this:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow()
window?.makeKeyAndVisible()
let currentUser = Auth.auth().currentUser
if currentUser != nil {
let board = UIStoryboard(name: "Main", bundle: nil)
let navigationController = board.instantiateViewController(withIdentifier: "navigationController") as! UINavigationController
window?.rootViewController = navigationController
}
return true
}
}

After deleting Main Storyboard.Add StoryBoard Programatically with Backward compatibility

My target for app is iOS 10.0As soon as I change my Target, I get a bunch of errors in SceneDelegate file. For backward compatibility, I added “#available(iOS 13.0, *)” for Scene Delegate class.
I Started with an OnboardingController. Which is totally programatic. So I deleted MainStoryboard and removed it from ”Main Interface” and ”Application Scene Manifest” in info.
Now, I have to set the rootView in both AppDelegate and SceneDelegate. If I don’t set window in SceneDelegate I am getting only Black screen in iOS 13.0+ Devices and if I don’t set in AppDelegate I get just Black screen in <13.0 Devices
Since I am calling both files my viewdidload() in Viewcontroller is called twice.
Following is what my AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "navC") as! UINavigationController
window?.rootViewController = viewController
window?.makeKeyAndVisible()
return true
}
}
Following is my SceneDelegate
#available(iOS 13.0, *)
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 }
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "HomeCollectionVC") as! HomeCollectionVC
window?.rootViewController = viewController
window?.makeKeyAndVisible()
}
...
}
am I missing something?
This can be found easily by searching, but here is an example...
Note: reference to SO user Matt
SceneDelegate.swift
import UIKit
// entire class is iOS 13+
#available(iOS 13.0, *)
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 }
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "HomeCollectionVC") as! HomeCollectionVC
window?.rootViewController = viewController
window?.makeKeyAndVisible()
}
func sceneDidDisconnect(_ scene: UIScene) {
}
func sceneDidBecomeActive(_ scene: UIScene) {
}
func sceneWillResignActive(_ scene: UIScene) {
}
func sceneWillEnterForeground(_ scene: UIScene) {
}
func sceneDidEnterBackground(_ scene: UIScene) {
}
}
AppDelegate.swift
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window : UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?)
-> Bool {
if #available(iOS 13, *) {
// do only pure app launch stuff, not interface stuff
} else {
self.window = UIWindow()
let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "navC") as! UINavigationController
window?.rootViewController = viewController
window?.makeKeyAndVisible()
}
return true
}
// MARK: UISceneSession Lifecycle
// iOS 13+ only
#available(iOS 13.0, *)
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
// iOS 13+ only
#available(iOS 13.0, *)
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}

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

Using scene delegate I'm able to set the root view controller.(I'm using Xcode 11.3 and iOS version 13.3 and running my app on iPhone 6+ with iOS 12.4)
What I want is when user login, I need to update the root view controller. For that I did the following
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
static let shared = SceneDelegate()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//some code is here
}
}
#available(iOS 13.0, *)
extension SceneDelegate {
func setRootViewControllerBasedOnLogin() {
if let isLoggedIn = UserDefaults.standard.bool(forKey: "isLogin"), isLoggedIn {
let tabbar = UIStoryboard(name: "Other", bundle: nil).instantiateViewController(withIdentifier: "Tabbar") as! UITabBarController
if var vcs = tabbar.viewControllers {
vcs.remove(at: 2)
tabbar.viewControllers = vcs
}
self.window?.rootViewController = tabbar
} else {
//other stuff
}
}
}
So when user login in to the app I need to remove a tab item from tab bar and update the root view controller.
So I'm doing as follows.
func processLogin() {
//performing login in this method so when login successful we setting root view controller
callLoginAPI { response in
if response.isSuccess {
UserDefaults.standard.set(true, forKey: "isLogin")
if #available(iOS 13.0, *) {
SceneDelegate.shared.setRootViewControllerBasedOnLogin()
} else {
// Fallback on earlier versions
}
}
}
}
When I'm doing this nothing happened. I'm not able to change the root view controller of the app after user successfully login into the app?
Any suggestions? what am I doing wrong?
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()
}
}
func presentYourView(from view: YourViwe) {
if #available(iOS 13, *) {
let mySceneDelegate = view.view.window?.windowScene?.delegate
if let sceneDelegate = mySceneDelegate as? SceneDelegate {
sceneDelegate.changeRootViewController(newViewController())
}
} else {
(UIApplication.shared.delegate as? AppDelegate)?.changeRootViewController(newViewController())
}
}
Update Swift 5+ , Xcode 13+
To change rootViewController depending upon user has logged in or not, here is the full code for SceneDelegate
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
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 UserDefaultHelper.isLoggedIn! {
print("User logged in")
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) // this assumes your storyboard is titled "Main.storyboard"
let yourVC = mainStoryboard.instantiateViewController(withIdentifier: "CustomerMainViewController") as! CustomerMainViewController // inside "YOUR_VC_IDENTIFIER" substitute the Storyboard ID you created in step 2 for the view controller you want to open here. And substitute YourViewController with the name of your view controller, like, for example, ViewController2.
self.window?.rootViewController = yourVC
self.window?.makeKeyAndVisible()
}
else {
print("User Not logged in")
}
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
The problem with PinkeshGjr's answer is that it discardings the window object provided by the scene. Here's what I feel is a better/simpler approach:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
static var current: AppDelegate {
return UIApplication.shared.delegate as! AppDelegate
}
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 13, *) {
} else {
window = UIWindow();
setUpRoot()
}
return true
}
func setUpRoot() {
window?.rootViewController = ViewController(nibName: nil, bundle: nil)
window?.makeKeyAndVisible()
}
}
#available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { fatalError() }
let firstWindow = windowScene.windows.first ?? UIWindow(windowScene: windowScene)
AppDelegate.current.window = firstWindow
AppDelegate.current.setUpRoot()
}
}

I am trying to get the rootViewController from the AppDelegate using the didFinishLoadingWithOptions method, but I can't access the window

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let navController = window.rootViewController as? UINavigationController,
let personController = navController.controllers[0] as? PeopleTableViewController{
print("something")
}
return true
}
But it is giving me an error, it is not recognizing the window property of the AppDelegate, did they change with IOS 13 because it works in my IOS 12 project
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "DataModel")
container.loadPersistentStores { description, error in
if let error = error {
fatalError("Unable to load persistent stores: \(error)")
}
}
return container
}()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let navController = window?.rootViewController as? UINavigationController,
let personController = navController.viewControllers[0] as? PeopleTableViewController{
personController.persistentContainer = persistentContainer
print("Nailed it ")
} else {
print("Sorry")
}
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
func applicationWillTerminate(_ application: UIApplication) {
persistentContainer.saveContextIfNeeded()
}
}
This is how my AppDelegate class looks
Here is how you set App's initialView Controller/rootViewController inside Scene Delegate class (this class handles Application Lifecycle Methods which were earlier handled by AppDelegate).
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let initialViewController = UIStoryboard(name: "Storyboard", bundle: nil).instantiateViewController(identifier: "DatePicker") as! DatePicker
let navigation = UINavigationController(rootViewController: initialViewController)
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = tabBarCnt
self.window = window
window.makeKeyAndVisible()
}
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
print("App entered Background")
}
}
I found out a way around. instead of putting your code in didFinsishLoadingWithOptions method, getting the UIview comptroller's persistentContainer variable and then passing the persistentContainer in AppDelegate. you can just add this to the top of the view Controller.
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.context
if you want the appDelegate you can access like this
let AppDelegate = UIApplication.shared.delegate as! AppDelegate
This works both IOS12 and IOS13 so, you don't have to change your code

Resources