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'
}
Related
My iOS application using Objective C. It received a remote notification, when a user click it, it will open a particular view.
This view has a back button in the navigation tab. In normal scenarios can back to root view. However, when I open this view from a notification, cannot back to any view nor leaving the application.
Here is my spruce code:
1. from AppDelegate.m to open a particular view:
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *viewAnnouncement =[storyboard instantiateViewControllerWithIdentifier:#"Announcement"];
self.window.rootViewController = viewAnnouncement;
[self.window makeKeyAndVisible];
Action function to back to previous view (in viewClass.m):
- (IBAction)onBtnBackClicked:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
I believe the problem is, when I open this view from a notification, no parent view to let it back to. Can anyone help me, when user open this view from a notification, whether it goes to a root view or back to the previous opening app
EDIT : The previous answer didn't take in count the fact that you were in AppDelegate. Maybe this answer is better.
Turn your [self dismissViewControllerAnimated:YES completion:nil]
into :
UINavigationController *navigationController = [[UINavigationController alloc] init];
UIViewController *yourPreviousVC = [[UIViewController alloc] init];
[navigationController pushViewController:yourPreviousVC animated:YES];
If I'm wrong or if I missunderstood the question, do not hesitate to correct my answer.
Try this
Announcement *annouce = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"Announcement"];
HomeVC *home = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"HomeVC"];
[((UINavigationController *)self.window.rootViewController) setViewControllers:#[home,annouce] animated:YES];
And If app already open and you want to go previous page
Announcement *annouce = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"Announcement"];
UIViewController *visibleView = ((UINavigationController *)window.rootViewController).visibleViewController;
[((UINavigationController *)self.window.rootViewController) setViewControllers:#[visibleView,annouce] animated:YES];
I found the solution. My app has multiple views which I can go from a navigation list. When I want to back to main view, I just dismiss the current view.
Cuurently, a view is opened when I click a notification and I can't simply dismiss this view, Coz it considered as root view.
What I did to solve it, I connect my view (Announcement) by segue to main view, and set an identifier (for example: "leaveAnnouncementPage". Then, in your class just call this function in back button's fnction to back to the main view:
[self performSegueWithIdentifier:#"leaveAnnouncementPage" sender:self];
Here is also some useful links:
First
Second
Thank you guys for your help, and all the best
I am making a messaging app on ios that will have multiple folders for different types of messages. I will be using a navigation controller structure and would like the root view to be where the user can choose which folder to view. However, when I first segue to the navigation controller I would like the inbox folder view to display directly (ie. bypass the root view). Apples mail app has a similar structure (launches inbox when it opens). how can I do this?
It really depends on how the relationship is between the first view controller and the second view controller. If you want to do something like this, why don't you put your second view controller as the root controller of the UINavigationController.
Anyway, if you still want to do it the way you describe, you can just direct to the second view controller using the viewDidLoad method from your root view controller. But, it will make the UI looks clumsy.
try to use this code :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
UIStoryboard *board = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ViewController *controller1 = [board instantiateViewControllerWithIdentifier:#"firstView"];
UINavigationController *navController = [[UINavigationController alloc]initWithRootViewController:controller1];
SecondViewController *secondView = [board instantiateViewControllerWithIdentifier:#"secondView"];
[controller1 addChildViewController:secondView];
[self.window setRootViewController:navController];
return YES;
}
Hope this will help you.
Write this code in didFinishLaunchingWithOptions of AppDelegate.m
UIStoryboard *MainStoryboard = [UIStoryboard storyboardWithName:#"Main"
bundle: nil];
UINavigationController *controller = (UINavigationController*)[MainStoryboard
instantiateViewControllerWithIdentifier: #"YourStoryBoardID"];
NeededViewController *need=[MainStoryboard instantiateViewControllerWithIdentifier:#"YourStoryboardID"];
[controller setViewControllers:[NSArray arrayWithObject:need] animated:YES];
self.window.rootViewController=controller;
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];
I recently work on a tutorial for my app. I created the tutorial with this Tutorial:
http://www.appcoda.com/uipageviewcontroller-tutorial-intro/
I created a "button" which brings the user back to the rootViewController, in this case the TabBarController. Problem is: with this tutorial I made a extra storyboard for the tutorial. So how can I go back to the original rootViewController(TabBarController) with the button?
Code:
- (IBAction)start:(id)sender {
UIViewController* backToRootViewController = [[UIViewController alloc] initWithNibName:#"TabBarController" bundle:[NSBundle mainBundle]];
[self.view addSubview:backToRootViewController.view];
}
This does not work, too
- (IBAction)start:(id)sender {
[self.navigationController popToRootViewControllerAnimated:YES];
}
EDIT
To open the tutorial at the first start:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
BOOL isAccepted = [standardUserDefaults boolForKey:#"iHaveAcceptedTheTerms"];
if (!isAccepted) {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[APPViewController alloc] initWithNibName:#"APPViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
}
APPViewController is the Tutorial
EDIT
After the help of johnnelm9r the current code is this:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"main" bundle: nil];
IntroViewController *introViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"IntroViewController"];
BOOL isAccepted = [standardUserDefaults boolForKey:#"iHaveAcceptedTheTerms"];
if (!isAccepted) {
[self.window.rootViewController presentViewController:introViewController animated:NO completion:nil];
}
But now, sadly, the app crashed and the error is:
Application tried to present a nil modal view controller on target <UITabBarController: 0x175409d0>.'
and also a warning: Incompatible pointer type assigning to 'ViewController' from 'UIViewController' in AppDelegate
*****UPDATE****
I uploaded a sample project to demonstrate what I mean. You can find it here: github link
Good luck!
**** EDIT
After talking with you more I'd suggest trying to use something similar to this code in your viewDidAppear method of whatever view controller runs the first tab of your tabbarcontroller (obviously you want your own class name where I have IntroViewController):
BOOL didShowIntro = [[NSUserDefaults standardUserDefaults] boolForKey:#"showIntro"];
IntroViewController *introViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"IntroViewController"];
if (didShowIntro)
{
[self presentViewController:introViewController animated:NO completion:nil];
}
and then just pop the presented controller from your button in the tutorial like so:
[self dismissViewControllerAnimated:NO completion:nil];
Just remember to set your user defaults to no when you press the button in your tutorial view controller. And make sure you are going from tabbarcontroller to navigationcontroller as a relationship segue and then to the view controller you want to show after the tutorial as a root. Just to be clear: there should be no connection to the tutorial view controller on your storyboard.
Just to be extra clear: your storyboard should have the navbarcontroller as your initial view controller connected to a navigationcontroller connected to a view controller and another view controller not connected to anything. :) And make sure you delete the code in the delegate.
To go to rootViewController
[self.navigationController popToRootViewControllerAnimated:YES];
Solved:
once the user has logged in / signed up, use the following code to transition to the main storyboard...
UIWindow* window = [[UIApplication sharedApplication] keyWindow];
window.rootViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]] instantiateInitialViewController];
I have the following UINavigationController flow to handle logging in...the top segue after the tabbarcontroller goes to a uinavigationcontroller that is the root for a viewcontroller.
When the user is already logged in the "this segue works" is executed so that the user doesn't have to log in at the login screen. That works perfectly. The issue I run into is when the user has to login...the segue from the login/signup screen to the login screen works perfectly, but when I go from the login screen to the tabbar the following happens:
This really shouldn't happen because I have the following code in my viewcontroller.m (gotten to here is called)
- (void)viewDidLoad {
...
self.navigationItem.title = #"Messages";
self.navigationItem.hidesBackButton = YES;
NSLog(#"gotten to here");
...
}
Does anybody know why this is happening?
I like to keep my login flow separate from the normal app flow. This means that I don't link a segue from the login screen to the app, but I handle that in my AppDelegate:
if ([MyUserHandler sharedHelper].isAuthenticated) {
[self presentMainInterface];
} else {
[self presentWelcomeInterface];
}
Where the first method does this:
- (void)presentMainInterface
{
self.window.rootViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]] instantiateInitialViewController];
}
and the other presents the login screen:
- (void)presentWelcomeInterface
{
UIViewController* rootController = [[UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:#"loginScreen"];
UINavigationController* navigation = [[UINavigationController alloc] initWithRootViewController:rootController];
self.window.rootViewController = navigation;
}
This way the login screen is loaded only when the user is not authenticated.
Make a custom class (sub class) of UITabBarControler and assign that custom class for your tabbarcontroller in storyboard. Then In viewDidLoad of custom tabbarcontroller add your code
self.navigationItem.hidesBackButton = YES;
It will work fine.