Prior to Xcode 11, one could conditionally set which Storyboard to start with on an iOS app inside AppDelegate. Now with the addition of SceneDelegate it is to my understanding that you can instantiate that same logic there, which I am having issues with.
If I set the Storyboard Name inside Application Scene Manifest I can get the app to always start on the same Storyboard, but I want to conditionally start on one of two Storyboards.
I've tried something like this both with Storyboard Name set and not set inside Application Scene Manifest but can't seem to get anything to work. Any Ideas?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = scene as? UIWindowScene else {return}
let storyboard: UIStoryboard
let initViewController: UIViewController
if (User.sharedInstance.isLoggedIn()) {
storyboard = UIStoryboard(name: "Main", bundle: nil)
initViewController = storyboard.instantiateViewController(withIdentifier: "MainTabs") as UIViewController
self.window?.rootViewController = initViewController
} else {
storyboard = UIStoryboard(name: "Login", bundle: nil)
initViewController = storyboard.instantiateViewController(withIdentifier: "LoginLanding") as UIViewController
self.window?.rootViewController = initViewController
}
self.window?.makeKeyAndVisible()
}
Looks like the problem is I wasn't setting the window with the scene.
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 windowScene = scene as? UIWindowScene else {return}
window = UIWindow(windowScene: windowScene)
// if user is not logged in then don't use Main storyboard
if (!User.sharedInstance.isLoggedIn()) {
let storyboard = UIStoryboard(name: "Login", bundle: nil)
window?.rootViewController = storyboard.instantiateViewController(withIdentifier: "LoginLanding")
}
}
Related
I had a working program consisting basically of a table view controller embedded in a navigation controller, and I decided to try to get rid to some mysterious/obnoxious warnings that first appeared in iOS 16 ("UINavigationBar decoded as unlocked for UINavigationController, or navigationBar delegate set up incorrectly"). Following online suggestions, I got rid of the navigation controller on the storyboard, and added some code to
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window?.windowScene = windowScene
window?.makeKeyAndVisible()
// ViewController: UITableViewController is my VC class
let viewController = ViewController()
let navViewController = UINavigationController(rootViewController: viewController)
window?.rootViewController = navViewController
}
to embed it programmatically. The program would then crash with a message that stating that it could not deque a cell with identifier "itemCell", and "must register a nib or a class for the identifier or connect a prototype cell in a storyboard". I solved that by adding the line
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "itemCell")
in ViewDidLoad but I don't understand why this was necessary, since the identifier for the prototype cell is still there in the storyboard, just as it was before. Can someone illuminate me? Thanks.
This:
let viewController = ViewController()
let navViewController = UINavigationController(rootViewController: viewController)
window?.rootViewController = navViewController
won't give you what you want.
In Storyboard, you have to give ViewController a Storyboard ID (such as "FirstViewController"), then instantiate it via code:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "FirstViewController")
let navViewController = UINavigationController(rootViewController: initialViewController)
Edit
Assuming the view controller we ID as "FirstViewController" in Storyboard is a table view controller, and we want to load it as the "root" controller for a navigation controller, the full code block would be:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window?.windowScene = windowScene
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "FirstViewController")
let navViewController = UINavigationController(rootViewController: initialViewController)
window?.rootViewController = navViewController
window?.makeKeyAndVisible()
}
i have a button in viewcontroller which navigates to editPage viewcontrooler
i want to use pushNavigation
i did below code in button action method
#IBAction func editBtn(_ sender: Any) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "editProfile") as! editProfile
let navigation = UINavigationController(rootViewController: newViewController)
self.navigationController?.pushViewController(navigation, animated: true)
}
but when i click on button it dosent navigate
i dont have navigation controler in storyboard.......
i dont want to present view controler
Looks like you are setting your viewController as initail View Controller from storyboard but do not embed this in navigation controller , either embed your viewController in navigationController and set this navigation controller as initialViewController or follow this approach as you don't want to do it from storyboard:
In SceneDelegate , write :
var window: UIWindow?
and then in this method :
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 }
//Setup user state
if let vc = UIStoryboard.init(name: "YOUR_STORYBOARD_NAME", bundle: nil).instantiateViewController(withIdentifier: "VIEW_CONTROLLER_IDENTIFIER") as? ViewController {
let nav = UINavigationController(rootViewController: vc)
window?.rootViewController = nav
window?.makeKeyAndVisible()
}
}
for IOS < 13, in AppDelegate.swift :
var window : UIWindow?
//MARK:- DID FINISH LAUNCHING WITH OPTIONS : CALLED EVERYTIME WHEN APPLICATION LAUNCHES
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let vc = UIStoryboard.init(name: "YOUR_STORYBOARD_NAME", bundle: nil).instantiateViewController(withIdentifier: "VIEW_CONTROLLER_IDENTIFIER") as? ViewController {
let nav = UINavigationController(rootViewController: vc)
window?.rootViewController = nav
window?.makeKeyAndVisible()
}
return true
}
I have 2 storyboards: Onboarding and Main. Everything works great as far as execution goes. The issue is that I have loading screen at the start of Main.storyboard that looks different depending on which storyboard launches.
Scenario 1: New user launches the app, it goes through Onboarding.storyboard then Main.storyboard launches and the loading screen looks as it should like this: Correct Screen
Scenario 2: User launches the app for the second time, therefore Onboarding.storyboard is not launched: Loading screen is off centre like this: Wrong Screen
Long story short: If there is no Onboarding.storyboard, it doesn't look right. The issue is only shown on iPad landscape; portrait looks fine.
All of my loading screen code is inside my first view controller of Main.Storyboard so I'm guessing that the issue is the order in which the code is called?
Onboarding.Storyboard only has 1 view controller
Main.Storyboard goes: TabBarController -> NavController -> FirstVC
I placed my SceneDelegate code below along with the loading screen code of FirstVC.
In case you want to dig in deeper, I'm using RevealingSplashView available on github. Since I don't think that's the issue I didn't get into that further.
SceneDelegate:
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 onboardingStoryboard = UIStoryboard(name: "Onboarding", bundle: nil)
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
var vc: UIViewController
if UserDefaults.standard.value(forKey: "firstTimer") == nil {
vc = onboardingStoryboard.instantiateInitialViewController()!
} else {
vc = mainStoryboard.instantiateInitialViewController()!
}
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
}
FirstVC relevant code:
class firstVC: UIViewController {
var revealingSplashView : RevealingSplashView!
override func viewDidLoad() {
super.viewDidLoad()
showLoadingScreen()
}
func showLoadingScreen() {
revealingSplashView = RevealingSplashView(iconImage: UIImage(named: "Icon")!, iconInitialSize: CGSize(width: 150, height: 150), backgroundImage: UIImage(named: "loadBackground")!)
revealingSplashView.animationType = .heartBeat
revealingSplashView.startAnimation()
view.addSubview(revealingSplashView)
}
Check to see that your layout constraints are the same in both of your storyboards. I'd recommend just setting them to center horizontal and center vertical on both of them.
In your scene delegate callback, why are you calling instantiateInitialViewController() twice per storyboard?
In my iOS 12 project, I used storyboards to kick things off. In app delegate didFinishLaunching, I would override and set the window if and only if it was a new user. So my "Main" storyboard would kick in most of the time.
Same technique could be applied here. Check out the Info.plist values you need to set for UIScene storyboard to work. You can override the window if you want to show the first time onboarding, otherwise let the system instantiate your main storyboard.
Further testing revealed that if I return SceneDelegate to its "factory default" state, therefore launching Main.storyboard, I had no issues.
To fix the issue I simply changed my SceneDelegate to:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
if UserDefaults.standard.value(forKey: "firstTimer") == nil {
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
let onboardingStoryboard = UIStoryboard(name: "Onboarding", bundle: nil)
var vc: UIViewController
vc = onboardingStoryboard.instantiateInitialViewController()!
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
}
}
My IOS simulator is black when I load my app. My Main Interface is set to the correct storyboard. I have a storyboard entry point setup also.
The home screen works but my app does not. Any suggestions
I have googled but can not find the fix to my question. Thanks!
Check if your app is using SceneDelegate class & also check if there exists a AppManifest File in Info.plist .If that's the case you have to provide App's Entry point inside Scene Delegate Class in method.
For Example
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()
}
}
This is the storyboard layout I have right now
So what I need is to check if the user is already authenticated when the app launches.
If he is not logged in set the root view controller to the navigation controller with the login and register forms.
If he is logged in set it to the tab bar view controller.
I tried a lot of different solutions but none of them worked. It just kept setting the view controller to the one marked with "Is Initial View Controller".
This is the code I tried in the AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
var viewController: UIViewController
if isAuthenticated() {
viewController = storyboard.instantiateViewController(withIdentifier: Constants.Storyboards.homeViewController) as! UITabBarController
} else {
viewController = storyboard.instantiateViewController(withIdentifier: Constants.Storyboards.authViewController) as! UINavigationController
}
self.window?.rootViewController = viewController
self.window?.makeKeyAndVisible()
return true
}
I can do this by pressing a button easily, but I want it to happen without the user needing to do something.
EDIT: Thanks to #LukaCefarin and #Francesco Deliro I managed to find out that the problem was. I was using XCode 11 and the rootViewController had to be set in the SceneDelegate.swift
This is what my code in the SceneDelegate.swift looks like for anyone who has a similar issue:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: scene as! UIWindowScene)
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
var viewController: UIViewController;
if isAuthenticated() {
viewController = storyboard.instantiateViewController(withIdentifier: "HomeVC")
} else {
viewController = storyboard.instantiateViewController(withIdentifier: "AuthVC")
}
self.window?.rootViewController = viewController
self.window?.makeKeyAndVisible()
}
EDIT:
This solution and setting the window rootViewController in the AppDelegate work for versions prior to Xcode11 and iOS13. As suggested in the comments by Luka Cefarin, if you are using Xcode11 and iOS13 you have to set the window rootViewController in the SceneDelegate.swift file.
You have to remove the Main Interface and to uncheck the initial view controller in the storyboard:
Before
After