IOS Change rootview controller after login - ios

I have implemented a side drawer in my application which required me to set the root view controller in my application delegate. i am now working on a login screen. to access the login screen i had to comment out where i set the root view controller. How can i set the root view controller once the user has logged in?
Below is my app delegate(or part of it)
AppDelegate
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let centerViewController = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController") as!ViewController
let drawerViewController = mainStoryboard.instantiateViewControllerWithIdentifier("DrawerViewController") as!DrawerViewController
let leftSideNav = UINavigationController(rootViewController: drawerViewController)
let centerNav = UINavigationController(rootViewController: centerViewController)
centerContainer = MMDrawerController(centerViewController: centerNav, leftDrawerViewController: leftSideNav)
centerContainer!.openDrawerGestureModeMask = MMOpenDrawerGestureMode.PanningCenterView;
centerContainer!.closeDrawerGestureModeMask = MMCloseDrawerGestureMode.PanningCenterView;
//window!.rootViewController = centerContainer
window!.makeKeyAndVisible()
return FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
}
i have a method to handle the segue after successful login but don't know how to set the root controller out side the delegate.
in summary the navigation works if i uncomment the //window!.rootViewController = centerContainer line but then cant access the login view

You can swap the root view controller dynamically:
self.view.window!.rootViewController = newVc
A simple example can be found here: https://stackoverflow.com/a/34951197/218152.
A complete example can be found here: https://stackoverflow.com/a/32109767/218152.
Swapping the rootViewController is a one time operation: no back button, no animation.

To change rootViewController after login screen
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
UIApplication.shared.windows.first?.rootViewController = viewController
UIApplication.shared.windows.first?.makeKeyAndVisible()
Make sure you have set 'HomeViewController' in storyboard as Storyboard ID of HomeViewController

Related

Swift storyboard login order

I am relatively new to Swift and I don't have much experience with the events and functions. After investigating a little I started working on a login screen on an existing app that didn't have it before. Here's what I decided to do.
My flow goes in this order:
Login screen
After successful login I save the session info
After login screen I show the main screen using a segue
If I close and reopen the app, my login screen is still the start screen, but inside viewDidAppear I have a session check and if the session exists I perform the segue to show the main controller.
I've seen that users do this the other way around - showing the main screen first and if there's no login session they cover it with the login screen or basically show the login screen first.
In my way of doing this, what I don't like is that the login screen always appears, although to be honest it does the job for this app in specific.
Is there a way to do this without the login screen appearing when there's a session? How is this ideally done in terms of order: login screen first or login screen second? And also, what is better to use, a navigation controller for the changes or segues are enough?
many here say is not recomended doing this way but i'll use appDelegate for this, in didFinishLaunchingWithOptions
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if requestManager.instance.token != nil {
if requestManager.instance.user.birthDate != nil && requestManager.instance.user.iscomplete() {
print("GOING TO TABBARVC")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "TabBarVC")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}else if requestManager.instance.user.birthDate != nil && !requestManager.instance.user.iscomplete(){
print("GOING TO DATANC")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "TabBarVC")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}else{
print("GOING TO REGISTER")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "register")
//loginRequest.instance.delegate = initialViewController as profileViewController
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}
}
so what i check here is for token saved in userdefault. could be checked from keycahin is it's critical, and depeding on what is found may show one or another screen, the last case is just in case where no data at all going to register or login screen.
so you could check for session is there is a session amke window show the main screen no session go to login screen
just like this in code:
if sesion != nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "MainVC")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}else{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "loginVC")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}
where MainVC is te id in the storyBoard for the mainViewcontroller and loginVC is the storyboard Idetntifier for loginViewController.

iOS: NSUserDefault not loading fast enough in AppDelegate? Conditional fails intermittently

I'm using NSUserDefault in AppDelegate to decide which VC to show to a user first after they load my app.
If user hasn't set a specific setting, they go to VC 1 where they set that.
If user has the setting, they go to VC 2 and on.
My problem is that sometimes I'm being sent to VC1 when I know I have the setting.
This leads me to believe that NSUserDefaults are not being fully loaded before my conditional checks for the value.
Could this be possible and if so is there a way to "block" the load process until the variable is fully loaded?
Here's the relevant code from AppDelegate that I'm using:
class AppDelegate: UIResponder, UIApplicationDelegate {
let prefs = NSUserDefaults.standardUserDefaults()
var window: UIWindow?
func updateNavBar(navBar: UINavigationBar) {
navBar.barTintColor = UIColor(netHex:0x1d8696)
navBar.tintColor = UIColor(netHex:0xAEFFDD)
navBar.titleTextAttributes = [NSForegroundColorAttributeName : UIColor(netHex:0xAEFFDD),NSFontAttributeName : UIFont(name: "Lato-Bold", size: 16)!]
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
FIRApp.configure()
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
self.updateNavBar(UINavigationBar.appearance())
if let team = self.prefs.stringForKey("team")
{
print("Team prefs found. (" + team + ") User sent to IVList")
// show IVList VC
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) // this assumes your storyboard is titled "Main.storyboard"
let yourVC = mainStoryboard.instantiateViewControllerWithIdentifier("TabBarController") as! UITabBarController // 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.
appDelegate.window?.rootViewController = yourVC
appDelegate.window?.makeKeyAndVisible()
}else{
// show editProfile VC
print("No team prefs found. User sent to profile Edit")
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) // this assumes your storyboard is titled "Main.storyboard"
let yourVC = mainStoryboard.instantiateViewControllerWithIdentifier("editProfile") as! EditProfileViewController // 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.
appDelegate.window?.rootViewController = yourVC
appDelegate.window?.makeKeyAndVisible()
}
return true
}
I think it's a load timing thing because the issue is intermittent and inconsistent. It happens about 20% of the time.
I recently had exactly this issue.
The simple answer is that didFinishLaunchingWithOptions() in the appDelegate is only called AFTER the nib has been loaded for your initial view controller.
See the discussion at iOS - viewDidLoad is being called BEFORE the didFinishLaunchingWithOptions delegate?
Hope that helps!

Setting rootViewController then navigate to next view programatically

Initially I have a hierarchy below after login
-> MyCoursesViewController
-> CourseInfo UITabBarController
If the user closes the app, then re-enters, the rootViewController will be the CourseInfo UITabBarController which is correct. However when the user needs to view a different course (exits the course), they can’t go ‘back’ to MyCoursesViewController because its no longer on the stack.
In AppDelegate:
if (inCourse) {
let storyboard : UIStoryboard = UIStoryboard(name: “Main”, bundle: nil)
let courseInfoTabController = storyboard.instantiateViewControllerWithIdentifier(“CourseInfo”) as! UITabBarController
self.window?.rootViewController = courseInfoTabController
} else {
let storyboard : UIStoryboard = UIStoryboard(name: “Main”, bundle: nil)
let myCoursesViewController = storyboard.instantiateViewControllerWithIdentifier(“MyCourses”)
self.window?.rootViewController = myCoursesViewController
}
Is there some way I can put the MyCoursesViewController as the rootViewController then automatically navigate to Course Info UITabBarController just so the MyCoursesViewController is on the hierarchy incase they hit back (exits the course)?
Alternatively is it better if the user exits the course (hit back), we delete the rootViewController somehow and replace with a new rootViewController? Another option is if we just replace the rootViewController, will the old one be freed from memory or is it still referenced somewhere?
e.g.
CourseInfo UITabBarController is currently still rootViewController but now we swap it out with a new one
let mainStoryBoard = UIStoryboard(name: "Main", bundle: nil)
let myCoursesViewController = mainStoryBoard.instantiateViewControllerWithIdentifier(“MyCourses”) as! ViewController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = myCoursesViewController
In your AppDelegate you can set your hierarchy. Try with something like:
let storyboard : UIStoryboard = UIStoryboard(name: “Main”, bundle: nil)
let myCoursesViewController = storyboard.instantiateViewControllerWithIdentifier(“MyCourses”)
if isInCourse{
let courseInfoTabController = storyboard.instantiateViewControllerWithIdentifier(“CourseInfo”) as! UITabBarController
let navigationBar = UINavigationController()
navigationBar.setViewControllers([myCoursesViewController,courseInfoTabController], animated: false)
self.window?.rootViewController = navigationBar
}else{
self.window.rootViewController = myCoursesViewController
}

revealViewController() always returns nil

I'm having some troubles with revealViewController in Xcode 7.2 and iOS 9.2.
My app starts with a view controller embedded in a navigation controller to perform a login.
After login, or if the login token is present, I jump to another view controller embedded in a navigation controller with the following code:
let homePage = self.storyboard?.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
let homePageNav = UINavigationController(rootViewController: homePage)
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = homePageNav
In this home view controller I would like to have a left navigation menu with SWRealViewController.
I had the SWRealViewController view linked with sw_front to my home navigation controller, and the following code:
if (self.revealViewController() != nil) {
self.menuButton.target = self.revealViewController()
self.menuButton.action = "revealToggle:"
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
But self.revealViewController() always returns nil, so it does not work.
I think I lost the revealViewController somewhere (maybe when I jump from the first navigation controller to the second) but I do not know what to do.
The most convenient to be a reason for the revealViewController to be nil
is you didn't connect segues correctly in stroyboard.
See this tutorial it's quite easy to follow.
Update
If in your case you just need to open a login vc if the user is not logged in you may do like this:
in AppDelegate
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
var rootVCStoryboardId = userIsLoggedin ? "SWRevealViewController" : "LoginViewController"
self.window?.rootViewController = UIStoryboard(name: Storyboards.main, bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier(rootVCStoryboardId)
Where SWRevealViewController is the stroyboard id for SWRevealViewController and LoginViewController is the storyboard id for your login view controller(or its navigation controller if exists).
In case someone is wondering how to do a manual segue, this is what worked for me at the end.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let sw = storyboard.instantiateViewControllerWithIdentifier("SWRevealViewController") as! SWRevealViewController
self.view.window?.rootViewController = sw
let destinationController = self.storyboard?.instantiateViewControllerWithIdentifier("StoryboardID") as! NameOfViewController
let navigationController = UINavigationController(rootViewController: destinationController)
sw.pushFrontViewController(navigationController, animated: true)
Incase you are skipping login scene based on the current user information, then make sure to instantiate the storyboard with SWRevealViewController. See below code for reference:
if User.currentUser != nil {
//There is a current user
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("SWRevealViewController")
window?.rootViewController = vc
}
else{
//No current user
}

Checking if ViewController is already in background or not

I have an app which starts with different VCs depending whether the user is already logged in or not.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow.init(frame: UIScreen.mainScreen().bounds)
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let initialViewController: UIViewController
if DataManager.getInstance().getUserInfo() == nil {
initialViewController = storyboard.instantiateViewControllerWithIdentifier("authenticationViewController")
} else {
initialViewController = storyboard.instantiateViewControllerWithIdentifier("locationsNavigationViewController")
}
window!.rootViewController = initialViewController;
window!.makeKeyAndVisible();
return true
}
If the user is not logged in, the app starts with AuthenticationViewController, otherwise it starts with LocationsNavigationViewController, which is a NavigationViewController
In the latter VC, the is a button for logout. The problem is when the user taps on that button, I don't know if I have to dismiss the LocationsNavigationViewController (because AuthenticationViewController is in background) or if I have to dismiss LocationsNavigationViewController and perform a segue for opening the AuthenticationViewController.
So far, I have just covered the first use case. So in LocationsNavigationViewController I call this function
func showAuthentication() {
dismissViewControllerAnimated(true, completion: nil)
}
But when the app starts with LocationsNavigationViewController dismiss the VC is not enough of course, because the the AuthenticationViewController has never been instantiated.
How can I solve this please?
self.navigationController?.viewControllers
This is an array which will contain all your previous view controller. You can enumerate it and check whether your view controller is exist or not.
If you are using UINavigationController then you can check any UIViewController present or not!
let rootViewController = application.windows[0].rootViewController as! UINavigationController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if !rootViewController.viewControllers.contains(UIViewController_Class()){
let notificationVC = mainStoryboard.instantiateViewControllerWithIdentifier(constInstance.notificationsIdentifier) as! UIViewController_Class_Name
rootViewController.pushViewController(notificationVC, animated: false)
}
Hope this helps!
Thanks to Sohil's answer, I change the showAuthentication function in this way
func showAuthentication() {
//Since the app can start with different VC, I have to check which is the window root VC
if UIApplication.sharedApplication().windows[0].rootViewController is AuthenticationViewController {
dismissViewControllerAnimated(true, completion: nil)
} else {
performSegueWithIdentifier("authenticationSegue", sender: self)
}
}
And added a segue from the NavigationViewController to the AuthenticationViewController, called authenticationSegue
Pretty simple solution.
You can create one function in appdelegate for logout
and in logout function chanege rootviewcontroller of window like
func logoutUser()
{
var login: UIViewController?
login = LoginViewController(nibName : "LoginViewController", bundle : nil)
let nav = UINavigationController(rootViewController: login!)
self.window?.rootViewController = nav
}

Resources