I have divided my project into two storyboards:
Main.storyboard - For authenticated user. Also this is the default storyboard.
Login.storyboard - For non-authenticated user.
App delegate file:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if(user is authenticated)
{
[self presentLoginScreen:YES];
return YES;
}
Login Screen in app delegate file:
-(void)presentLoginScreen:(BOOL)animated{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Login" bundle:[NSBundle mainBundle]];
UIViewController *vc =[storyboard instantiateInitialViewController];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = vc;
}
On logout, login Screen is presented. I defined this in app delegate file:
-(void)logOut{
//clear data
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Login" bundle:[NSBundle mainBundle]];
UIViewController *vc =[storyboard instantiateInitialViewController];
self.window.rootViewController = vc;
//Show login Screen
[self presentLoginScreen:NO];
}
From viewcontroller of MainStoryBoard(letsay mainstoryboardVc.m), logOut is called as:
-(void)didTouchLogOut{
NSLog(#"GoodBye");
AppDelegate *appDelegateTemp = [[UIApplication sharedApplication]delegate];
[appDelegateTemp logOut];
}
This is causing a lot of issue:
Firstly, when I log out, and move to login screen, I can still see mainstoryboardVc.m in the background which looks very clumsy.
I log out, re-log in and again trying to log out, I see following message:
Presenting view controllers on detached view controllers is discouraged
and login screen is not presented.
I tried many answers on the web on view controller presentations between two storyboards without keeping history, nothing seems to work...
To swap out UIViewControllers there are a few other methods that need to be called when transitioning the root view controller:
Objective-c:
- (void)setRootViewController:(UIViewController *) newRootViewController {
UIViewController* currentViewController = self.window.rootViewController;
if (newRootViewController != currentViewController) {
[currentViewController willMoveToParentViewController:nil];
[currentViewController.view removeFromSuperview];
[currentViewController removeFromParentViewController];
self.window.rootViewController = newRootViewController;
}
}
Swift:
func setRootViewController(newRootViewController: UIViewController) {
if let currentViewController = self.window?.rootViewController {
if currentViewController != newRootViewController {
currentViewController.willMoveToParentViewController(nil)
currentViewController.view.removeFromSuperview()
currentViewController.removeFromParentViewController()
self.window?.rootViewController = currentViewController
}
}
}
Two UIViewController objects are duplicated because you set self.window.rootViewController for both of them. Try that;
Login
-(void)presentLoginScreen:(BOOL)animated{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Login" bundle:[NSBundle mainBundle]];
UIViewController *vc =[storyboard instantiateInitialViewController];
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
}
Logout
-(void)logOut{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Login" bundle:[NSBundle mainBundle]];
UIViewController *vc =[storyboard instantiateInitialViewController];
[self presentViewController:vc animated:YES completion:nil];
}
Related
I have a tab bar which contains five tabs. My application does not required user to be logged in. Only some features will be allowed if a user signups or logins.
If a user registered/logged in already, I implemented the following logic in the UserViewController which is connected to the NavigationController. However, in the following logic, user still could able to see that ViewController in one-two seconds.
-(void) viewWillAppear: (BOOL) animated
{
if(isRegistered)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UserProfileViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"UserProfileVewController"];
[self.navigationController pushViewController:vc animated:YES];
}
}
I wonder if a user registered already, how could I skip UserViewController?
I want tabbar clicks to open directly to the UserProfileVewController rather than UserViewController which is login/signup viewcontroller.
In the project's AppDelegate, you could check if user is logged/registered or not, and display the ViewControllers dependently:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
BOOL userIsLoggedIn = AMethodCheckIfUserLoggedIn();
if (userIsLoggedIn) {
UserProfileViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"UserProfileVewController"];
self.window.rootViewController = vc;
}
else{
UserViewController *userViewController = [storyboard instantiateViewControllerWithIdentifier:#"UserViewController"];
self.window.rootViewController = userViewController;
}
//... Other logic goes here
[self.window makeKeyAndVisible];
return YES;
}
You could try having your app delegate (or whomever you want really) conform to the UITabBarControllerDelegate and implement - (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController. In there you might be able to do the necessary checks/fiddling of the view controllers maybe.
In my project initial entry point in storyboard is LoginViewController. If user login to app I always want to show main ViewController of app by escaping LoginViewCntyroller, I did that following code in didFinishLaunchingWithOptions method.
if ([[NSUserDefaults standardUserDefaults] boolForKey:IS_SIGNIN_CHECK])
dispatch_async(dispatch_get_main_queue(), ^(void){
[self showMainScreen];
});
-(void) showMainScreen
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:[NSBundle mainBundle]];
SWRevealViewController *viewController = (SWRevealViewController *)[storyboard instantiateViewControllerWithIdentifier:#"MainScreenViewID"];
[self.window makeKeyAndVisible];
[self.window.rootViewController presentViewController:viewController
animated:NO
completion:nil];
}
It's working fine. But problem is when it goes to MainViewController, it goes to LoginViewController for few seconds and then shows MainViewController. How to avoid that transition form LoginViewController to MainViewController?
you are presenting your main screen through the LoginViewController. Means first the LoginViewController view will load and display to user and then it will redirect to main screen there is no way to avoid it using this scheme.
But you can avoid it by setting your main screen as rootViewController of main window and it will work fine
if ([[NSUserDefaults standardUserDefaults] boolForKey:IS_SIGNIN_CHECK])
[self showMainScreen];
-(void) showMainScreen
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:[NSBundle mainBundle]];
SWRevealViewController *viewController = (SWRevealViewController *)[storyboard instantiateViewControllerWithIdentifier:#"MainScreenViewID"];
self.window.rootViewController = viewController;
}
When i first come to Login Screen I store value in NSUserDefaults. When I press on signinButtonAction to move to DetailScreen it stucks and never moves forward.
I have made DetailScreen embedded in Navigationcontroller as InitialViewController even the next screen is not navigating to other screens when i re-run it again it comes to DetailScreen and then DetailScreen stucks to navigate to other screens.
How do I handle this?
My code is here:
AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"email"])
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
categoryVC *viewController = (categoryVC *)[storyboard instantiateViewControllerWithIdentifier:#"categoryVC"];
[self.window setRootViewController:viewController];
}
else
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
signInVC *viewController = (signInVC *)[storyboard instantiateViewControllerWithIdentifier:#"signInVC"];
[self.window setRootViewController:viewController];
}
in SigninButtonAction I am doing:
{
categoryVC * second= [self.storyboard instantiateViewControllerWithIdentifier:#"categoryVC"];
[self.navigationController pushViewController:second animated:YES];
}
I would not set RootViewController if you have such a workflow. I would instantiate main view controller as is, and if you need to show auth I would use presentViewController. Later on, you can do dismiss or something like that.
So, in you app delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if (nil == [[NSUserDefaults standardUserDefaults] objectForKey:#"email"])
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
categoryVC *viewController = (categoryVC *)[storyboard instantiateViewControllerWithIdentifier:#"signInVC"];
[self.window.rootViewController presentViewController:viewController animated:NO];
}
}
and then, when you do the auth and it passes:
[self dismissViewControllerAnimated:TRUE completion:nil];
In my AppDelegate.m, I have the following code to set the initial view controller for the user at launch, depending on whether there are credentials stored in the keychain.
If the user does have credentials stored, the app will instantiate the main user interface with storyboard id, balancescreen. This works fine with the code below.
If the user does not have credentials stored, I would like to present the login view controller with storyboard id, loginscreen, modally over the top of balancescreen. When correct credentials are entered, the view controller will be dismissed (I have already handled this), with the user shown the main interface. This is what I need help with, finding a solution to do this without glitching the application.
This is the code I currently have:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// grabs password for the only account
// (there shall always only be one set of credentials stored so this will work)
NSString *password = [SSKeychain passwordForService:#"MyOpal" account:[[SSKeychain accountsForService:#"MyOpal"][0] valueForKey:#"acct"]];
// if password has a length (aka user has previously used the application with credentials in keychain), direct to the app
// if password has no length (aka does not exist because user has not used the application before), direct to login screen
if (password.length > 0) {
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"balancescreen"];
self.window.rootViewController = viewController;
[self.window makeKeyAndVisible];
} else {
// do something here to present login view controller modally over the top of the main interface (with storyboard id, 'balance screen')
// therefore, when the user logs in, the login view controller will dismiss, with the user greeted at the main interface
}
return YES;
}
Edit: This is my storyboard set up:
Don't change the rootViewController in your application. Make the rootViewController receive the request to navigate to login or to application.
Changing the rootViewController of the window is bad practice as you need to do animations manually. A storyboard flow would look like this:
Try this:
if (password.length > 0) {
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"balancescreen"];
self.window.rootViewController = viewController;
[self.window makeKeyAndVisible];
} else {
// go on as before
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"balancescreen"];
// instantiate the LoginViewController
LoginViewController *loginViewController = [storyboard instantiateViewControllerWithIdentifier:#"LoginViewController"];
self.window.rootViewController = viewController;
[self.window makeKeyAndVisible];
// present the LoginViewController modoally with viewController as presenter
[viewController presentViewController:loginViewController animated:NO completion:nil];
}
Obviously I am assuming that your UIViewController that handles the login is called LoginViewController. If that's not the case, just go on and adjust its name in your code :)
Anyway, for this kind of situation I would rather recommend you to not present the login modally. A better approach is to actually change the rootViewController property of your application\s main UIWindow.
That could look like this in your case:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
if (password.length > 0) {
UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"balancescreen"];
self.window.rootViewController = viewController;
}
else {
LoginViewController *loginViewController = [storyboard instantiateViewControllerWithIdentifier:#"LoginViewController"];
self.window.rootViewController = loginViewController;
}
[self.window makeKeyAndVisible];
The goal:
When my app starts up - I need it to display a view before it gets to the "Home" screen. Its a tab bar application and this view is not part of the tabbar.
I am using Storyboards and Xcode 5 - iOS7 only app.
The problem:
I have code that will check if the app is first launch or not. Based on that, I then want to present a one time only view to the user.
What I have tried:
The following code is in the appDelegate of the application as this is where it all starts. I call the following bit of code in there:
-(void)showCountrySettings
{
if (self.termsHaveBeenAccepted){
BBCounterySettingsViewController *countrySettings = [[BBCounterySettingsViewController alloc]initWithNibName:#"View" bundle:nil];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"IDENTIFIER"];
[self.navigationController pushViewController:vc animated:YES];
}
I get compile errors as [self.navigationController..] doesn't exist. Nor does [self.tabbarcontroller...];
This is obvious as I don't have properties setup for these - but how do I go about resolving this and connecting the tab bar to the storyboard?
What am I missing?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
if(!isAgreementAccepted)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"IDD"];
self.window.rootViewController=vc;
}
return YES;
}
If agreement is not accepted set the T&C viewController as rootViewController when user click the accept button then set the TabBarviewController as root.
u can access the widow object through application delegate any where
[[[UIApplication sharedApplication]delegate] window].rootViewController=tabViewController.
Change the rootviewcontroller of window programaticaly
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIStoryboard *aStoryBoard=[UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
UITabBarController *aTabCtrl=[aStoryBoard instantiateViewControllerWithIdentifier:#"Tab"];
FirstVC *aFirstCtrl=[aStoryBoard instantiateViewControllerWithIdentifier:#"First"];
if(self.termsHaveBeenAccepted)
self.window.rootViewController=aFirstCtrl;
else
self.window.rootViewController=aTabCtrl;
return YES;
}
This will definitely work I have tested.
If Tabbarcontroller is your root viewcontroller, then using below code will resolve the issue in your appdelegate.
-(void)showCountrySettings
{
if (self.termsHaveBeenAccepted){
BBCounterySettingsViewController *countrySettings = [[BBCounterySettingsViewController alloc]initWithNibName:#"View" bundle:nil];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"IDENTIFIER"];
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
[tabBarController presentModalviewcontroller:vc animated:YES completionblock:nil];
}
add your viewcontroller superview of tabbarcontroller as first time else call tabbarcontroller alone