Why is manually setup root view controller showing black screen?
To ensure you see your root view controller in iOS 13 when everything is done programmatically, you must do the following:
In the scene delegate, you must create the window instance and the root view controller:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let winScene = (scene as? UIWindowScene) else { return }
// Create the root view controller as needed
let vc = ViewController()
let nc = UINavigationController(rootViewController: vc)
// Create the window. Be sure to use this initializer and not the frame one.
let win = UIWindow(windowScene: winScene)
win.rootViewController = nc
win.makeKeyAndVisible()
window = win
}
}
Your Info.plist has to have the "Application Scene Manifest" entry. Below it should be the "Enable Multiple Windows" entry. Set to YES or NO as appropriate to your app. Optionally you should also have the "Scene Configuration" entry.
All of these entries are added by Xcode when you check the "Supports multiple windows" setting on the General tab of your target. This will default the "Enable Multiple Windows" entry to YES so you can change that to NO if you want scenes but not multiple windows.
iOS - Setting 'rootViewController' results in black screen on device only
It looks like the ViewController is not set to display anything. Unless you are using a xib (in which case you would need to load the view controller in a different way, see below), there is nothing describing how the ViewController's view should render.
To test this out, you can add the line self.view.backgroundColor = UIColor.red
to the ViewController's viewDidLoad()
method, then run it again on the device- if the background color turns red, then hooray! The next step will be programmatically adding a UILabel.
Loading UIViewController From a XIB
let vc = MyViewController(nibName: "xibname", bundle: nil)
Alternatively, you can mask the loading by adding a custom init inside MyViewController
:
class MyViewController: UIViewController {
required init() {
super.init(nibName: "xibname", bundle: nil)
}
}
(Thank you zonily-jame for the addition of hiding it in the class)
Initial ViewController with Firebase bug fix
I think there are 2 ways to solve this issue.
1. Using a splash screen:
Don't make the decision of showing a login screen or main screen in scene(_:, session, connectionOptions)
. Instead, create a new view controller called SplashScreenViewController
. Set your app's logo in its center. And then in its viewDidLoad()
move your logic to check if the user is already logged in:
func chooseAndPresentStartScreen() {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
if let user = Auth.auth().currentUser {
FirestoreService.shared.getUserData(user: user) { (result) in
switch result {
case .success(let muser):
let navigationController = storyBoard.instantiateViewController(withIdentifier: "Navigation") as! UINavigationController
let conroller = storyBoard.instantiateViewController(withIdentifier: "MainController") as! MainController
navigationController.viewControllers = [conroller]
self.present(navigationController, animated: true, completion: nil)
case .failure(_):
let conroller = storyBoard.instantiateViewController(withIdentifier: "SignUpController") as! SignUpController
self.present(conroller, animated: true, completion: nil)
}
}
} else {
print(3)
let conroller = storyBoard.instantiateViewController(withIdentifier: "SignUpController") as! SignUpController
self.present(conroller, animated: true, completion: nil)
}
}
2. Using User Defaults:
Define a global variable for the key used to save user default value.
let isUserLoggedInKey: String = "IsUserLoggedIn"
After the user logs-in:
let defaults = UserDefaults.standard
defaults.setValue(true, forKey: isUserLoggedInKey)
Then in your scene(_:, session, connectionOptions)
, check if this value is set.
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 defaults = UserDefaults.standard
let isLoggedIn = defaults.bool(forKey: isUserLoggedInKey)
if isLoggedIn {
let navigationController = self.storyBoard.instantiateViewController(withIdentifier: "Navigation") as! UINavigationController
let conroller = self.storyBoard.instantiateViewController(withIdentifier: "MainController") as! MainController
navigationController.viewControllers = [conroller]
self.window?.rootViewController = navigationController
} else {
let conroller = self.storyBoard.instantiateViewController(withIdentifier: "SignUpController") as! SignUpController
self.window?.rootViewController = conroller
}
window?.makeKeyAndVisible()
}
Don't forget to set isLoggedInKey
to false when the user logs out.
let defaults = UserDefaults.standard
defaults.setValue(false, forKey: isUserLoggedInKey)
If you don't want to manually save value for isUserLoggedInKey
when the user logs in and logs out, you can also use a state change listener. It takes a callback that is triggered when the user logs in or logs out. Add this in your Login view controller:
Auth.auth().addStateDidChangeListener { (auth, user) in
let defaults = UserDefaults.standard
defaults.setValue(user != nil, forKey: isUserLoggedInKey)
}
Changed rootViewController but the view of old rootViewController is still in the View Hierarchy
The reason:
I have this problem when I try to replace rootViewController
while logged in using the Google Sign-In SDK and Facebook Login SDK.
These SDKs have an authentication screen like this one:
By using the Debug View Hierarchy, I realized when the authentication screen was presented (2), the app changed the rootViewController
to a UITransitionView
. And when the authentication screen is dismissed (3), it changed the rootViewController
once more to the state before presenting the authentication screen (1).
State (1): Before presenting the authentication screen, rootViewController
is LoginViewController
.
State (2): Presenting the authentication screen, rootViewController
changed to UITransitionView
.
State (3): After dismissed the authentication screen, rootViewController
has returned to LoginViewController
.
States (1) + (3) in Debug View Hierarchy:
State (2) in Debug View Hierarchy:
I put my code to change the rootViewController
in the each delegate method of corresponding SDKs that is called when authentication is completed.
Google Sign-In SDK: signIn:didSignInForUser:withError:
Facebook Login SDK: logInWithReadPermissions:fromViewController:handler:
These methods are called immediately after the user has authenticated without regard to the authentication screen is dismissed or not.
It means, sometimes, the problem occurs when the user's authentication process is completed too quickly, even before the authentication screen is dismiss and the rootViewController
changes to LoginViewController
. It means, the problem is in between two states (2) and (3), when the user has authenticated but rootViewController
is still UITransitionView
.
The solution:
Temporary, before I can find a better solution, I prevent the user's authentication process from happening too quickly, it means that I wait for state (3) to finish by delaying 0.25 seconds after the user had authenticated and then changing rootViewController
.
0.25 is enough time for everything to work and too fast for the user to lose patience.
UINavigationController shows black screen after pushing a view
The accepted answer didn't work for me. I was already using instantiateViewControllerWithIdentifier
to navigate to view controller in different storyboard. I am using xcode7.2 and Swift.
I am navigating from storyboard1, some view controller's button action.
Destination is to initial Navigation controller of Storyboard2.
Still I get black screen.
The problem was storyboard2's Navigation controller linked to 1st view controller was linked via show
.
Solution:
Delete the link between Nav controller and 1st view controller. Now link it using root view controller
. (Ctrl+Click on Navigation controller and drag it to the View controller and Select the option "root view controller")
Related Topics
No Such Module 'Restkit' With Cocoapods and Swift
Use Reserved Keyword a Enum Case
How String Comparison Happens in Swift
Swift 2 iOS 9 Do Catch Try Crashing with Unexpected Nil Found
Swift 2.1 [Uint8] --Utf8--> String
What's the Difference Between Struct Based and Class Based Singletons
Using as a Concrete Type Conforming to Protocol Anyobject Is Not Supported
Extension of Dictionary Where <String, Anyobject>
Is There a Prefix Header (Or Something with This Functionality) in Swift
Handling Multiple Gesturerecognizers
Split Now Complains About Missing "Isseparator"
Swift: Change the Cell's Uibutton Image with Tableview Didselect Method
What Is the Correct Date.Format for Mmm Dd, Yyyy Hh:Mm:Ss A? and How Convert to Dd-Mm-Yyyy Hh:Ii
How to Require That a Protocol Can Only Be Adopted by a Specific Class
Objective-C Bridging Header for Frameworks
Why Are Doubles Printed Differently in Dictionaries
Identify Face of a Cube Hit on Touches Began in Swift - Scenekit