Present modal view controller over view controller at launch - ios

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];

Related

Skip Login View Controller in the NavigationController

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.

iOS - Transition between different Storyboard view controllers

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];
}

Instantiate view controller from within navigation stack on launch

I have an app like this
Navigation controller -> login controller -> main view controller -> other stuff
I want to try to login in applicationdidfinishloadingwithoptions, and if that is successful then load main view controller, otherwise load the login view controller. My problem is I want the navigation stack to remain intact like above no matter what, so that I can pop back to my login view controller if I want to log out.
Right now I try to instantiate a main view controller on successful login, but on logout and other navigations it complains that I don't have a navigation controller embedded.
What is the correct way to do this?
If you are using storyboards then first of all create a UIViewController for Login and give it a storyboard ID, second create your mainViewController and embed it in UINavigationController and give storyboard id to UINavigationController.
After that in AppDelegate.m's applicationdidfinishloadingwithoptions load your appropriate VC based on user is logged in OR not.
Example
// Check if user is logged in
if ([[NSUserDefaults standardUserDefaults] stringForKey:#"loggedIn"] == NULL || [[[NSUserDefaults standardUserDefaults] stringForKey:#"loggedIn"] isEqualToString:#"false"]) {
// show login page
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *mainViewController = [storyboard instantiateViewControllerWithIdentifier:#"login"];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = mainViewController;
[self.window makeKeyAndVisible];
} else {
// show home page
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *mainViewController = [storyboard instantiateViewControllerWithIdentifier:#"home"];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = mainViewController;
[self.window makeKeyAndVisible];
}
Edit
So your stack will become like this
NavController->mainVC->OtherStuff
And standalone
LoginVC
Yogesh's answer worked well for me, however I found a more elegant way to carry out exactly what I needed. In applicationdidfinishlaunchingwithoptions I perform a test to log in. If I am logged in (test successful) I do push an instance of my main view controller onto the stack like this.
if(loggedIn) {
UIViewController *main = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:#"Main"];
[(UINavigationController *)self.window.rootViewController pushViewController:main animated:NO];
Navigation controller -> login controller -> main view controller -> other stuff
Change your Navigation Stack,
Navigation controller - > main view controller -> other stuff
So main view controller is the RootViewController of the navigation stack.
if(userloginstatus == YES)
{
Do other stuff, show other screens based on the flow.
}
else
{
Push or present 'loginviewcontroller' from the main view controller,
So after successfull login just pop the 'loginviewcontroller'
}

Loading new Storyboard and UIButton inactive

I added a new UIViewController to get a log in issue fixed and now the button in the NavigationBar on the next scene are inactive. Here is the code I used to load the new StoryBoard:
-(void)newSotrybooard{
UIStoryboard *settingsStoryboard = [UIStoryboard storyboardWithName:#"LogedIn" bundle:nil];
UIViewController *initialSettingsVC = [settingsStoryboard instantiateInitialViewController];
initialSettingsVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:initialSettingsVC animated:YES completion:NULL];
}
I added the new storyboard after I couldn't get the NavigationBar to show up and push without and error on the main storyboard. This is the only code that has changed. The Navigation Controller does not have a class associated with it and neither does the TableView to follow. I can also not scroll the Table view. Thank you for your help!
Update:
I updated the code above and
The error I receive is:
Warning: Attempt to present <UINavigationController: 0x109fa0ce0> on <PrivateViewController: 0x109f6ef80> whose view is not in the window hierarchy!
Based on the information you have provided, I surmise that the following could be helpful -
Yes, you have used instantiateViewControllerWithIdentifier.
But, remember it instantiates and returns the view controller with
the specified identifier. You're missing that.
Provide the same name to your new Storyboard in your code and
File Inspector (like you named it LogedIn above) and it
should be set in the Main Interface (Target >> General >> Main
Interface).
See rootViewController? The rootViewController
for the window needs to be assigned (either via Storyboard or
programmatically). Please check. I have added a UINavigationController in my example below -
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"StoryboardName" bundle:nil];
ViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"StoryboardName"];
vc.title = #"Is this title visible?";
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
self.window.rootViewController = nav;
return YES;
}
For the ViewController.m
-(void) goToSettings {
UIStoryboard *settingsStoryboard = [UIStoryboard storyboardWithName:#"SettingsStoryboard" bundle:nil];
UIViewController *initialSettingsVC = [settingsStoryboard instantiateInitialViewController];
initialSettingsVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:initialSettingsVC animated:YES completion:NULL];
}

Presenting a view for the first time in appDelegate

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

Resources