I am trying to change root controller on app delegate.
I am making a server call to check if login session is valid or not. If valid, show welcome page, else show login page.
In the appDelegate , I am making server call in the method didFinishLaunchingWithOptions.
I get response through my datamodel delegate, but by the time I get response from server, the old root controller is already presented on screen.
At present, old root controller is presented first and within a fraction of seconds, new root controller is presented! So it looks like a flickering thing!
Is there a way to hold the old root controller to present untill I get repsonse from server ?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
_dataModel=[DataModal sharedInstance];
_dataModel.delegate=self;
NSString *token=[[NSUserDefaults standardUserDefaults]objectForKey:#"token"];
if (!token) {
token=#"jkfhgjkfg908";
}else{
_dataModel.auth_token=token;
}
[_dataModel checkToken:token];
return YES;
}
//By the time it reaches here, the old root controller is already present on screen
-(void)checkToken:(NSDictionary *)items{
if ([items[#"success"] isEqual:#1]) {
AppDelegate *app = (AppDelegate*)[[UIApplication sharedApplication] delegate];
UIStoryboard *storyboard=[UIStoryboard storyboardWithName:#"Main" bundle:nil];
app.window.rootViewController=[storyboard instantiateViewControllerWithIdentifier:#"DashboardNavController"];
}else{
//do nothing, defualt is login view
}
}
You should not be waiting on a server call to decide on your root view controller - once didFinishLaunchingWithOptions is called you need to be presenting a view.
Have one Root View Controller present an initial view controller on load and stick to it - it's root for a reason.
If you have a token cached, skip your loginViewController and segue on. Plan for no internet/poor connection/ expired tokens, but don't put asynchronous actions in app delegate that prevent your UI being presented.
Uncheck Is initial View Controller checkbox for a controller in Main.storyboard
Initialize appDelegate's window property: appDelegate.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
Set rootViewController: appDelegate.window.rootViewController = controller;
And call makeKeyAndVisible method: [appDelegate.window makeKeyAndVisible];
Rather than changing rootViewController, using initialViewController as an entrance is a better approach in my opinion. When app is launched there must be a screen to make the user feel everything is going well. Also, you are making a request to server and this process may fail or timed out.
My advice is to make navigation through initialViewController. Make it visible and put some animation or activity indicator and then when the response come, if logged in, go to your apps mainViewController, else go to loginViewController. Also you can check internet connection here and advice user to connect to internet.
You cannot hold the old view controller to present, but instead you can present a view controller with black screen and then after you got the response form the server about the session you could present the view controller based on your needs.
Related
I am working on an application and I want to implement a tutorial that would run the firs time the application is run on the Phone. I have already setup NSUserDefaults and can successfully determine and flag the application once it is run for the first time. My problem now is moving from the Tutorial View Controller to the main view controller.
My current set up has the main view controller set as the root controller to my app and my tutorial view controller needs to be shown and then dismissed only the first time the app is run.
What's the best method for implementing it? I was thinking of showing it as a Modal view and have the main view controller show it if it detects the first run. Any suggestions?
Note that I am not using Storyboards
Simply put, choose your root view controller programmatically. Something like:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([self isFirstLaunch])
{
self.window.rootViewController = [TutorialViewController new];
[self setFirstLaunch NO];
}
else
{
self.window.rootViewController = [MyRegularRootViewController new]
}
[self.window makeKeyAndVisible];
return YES;
}
I would go with your approach of presenting a modal on top of your root view controller when you detect first launch.
Use presentViewController:animated:completion: to display it, with animated: set to NO. That way it will appear on top of the root view controller and cover it. Then when you're done with the first view controller you can dismiss it and reveal your root view controller underneath.
I am currently working on an iOS app that requires authentication. I have trouble transitioning to the login view when the invalid session occurred.
My idea to handle this is by notifying the app delegate that the session has became invalid; the app delegate will then take the user to the login screen.
In my current view controller, I have this code logic to detect the invalid session:
if (responseCode == INVALID_AUTHORIZATION) {
AppDelegate *appdelegate = (AppDelegate *)([UIApplication sharedApplication].delegate);
[appdelegate invalidateSession];
return;
}
In my app delegate, I handle the invalid authorization by taking the user to the root controller.
- (void) invalidateSession {
UINavigationController *navController = (UINavigationController *) self.window.rootViewController;
[navController popToRootViewControllerAnimated:YES];
}
But when I do this, I get the following error:
Unbalanced calls to begin/end appearance transitions for .
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
Any thought on what I did wrong here? Thanks!
I think you shouldn't implement the pop - push transitions in AppDelegate because you can't be sure if the previous transition has completed before popping the view controller - which causes the issue mentioned above.
In case you have to check the session in many screens, I suggest implementing a "super" view controller that has a method named checkSession where you do the check and pop the view controller when necessary. This method should be called in viewDidAppear just so you can make sure no transition is incomplete by the time you call popToRootViewController. Then you'll need to make all the view controllers that requires authenticating subclass that super view controller and you're good to go. I hope this is useful for you.
P.s: Since you have to complete displaying the views before checking session, I think maybe a popup mentioning an expired session would be neat for your UI.
I have a UINavigationController and on it I load a rootView that controlls the login process to my application.
I have recently added some code to my application delegate that checks my settings bundle for a logout request when this logout request happens I would like to either reload the rootView so that it loads the login hud, or just call the method inside rootView that shows the login hud.
This is how I set up the rootView for the navigationController inside my appdelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window.rootViewController = self.navigationController; //Adds RootViewController to the NavigationController interface
// etc
}
What I would like to know is there a way to reload rootViewController? or call a method from it inside the application delegate?
It can be done but it's complicated. Best to avoid it if possible, and the specific requirements will be different for every app. Without seeing the source code for your app we can't tell you how it's done.
The basic process is you need to remove all of them from the view and set all references to nil, and then re-create it either from code or by loading the nib again.
A far better option is to leave the rootViewController where it is, and present a modal login view controller over the top of it. Once the user has logged in, send an NSNotification that the root view controller can observe, and populate its data.
Wait until after the notification has been sent to hide the login controller, and consider having the root view controller block the main thread while it performs any network operations pertaining to logging in. This way the login view (with a "logging in..." message?) will remain visible until the root view is fully populated.
I am writing a turn-based game for the iOS platform. The client communicates with a remote server using the CocoaAsyncSocket API. Right now I work on this case: the client has been inactive for a while, and has been disconnected from the server due to timeout. If that's the case, I wish to pop back to the login view when the app enters the foreground, to let the user log back in again.
I assume I have to do this kind of work in the -(void)applicationWillEnterForeground of my app's delegate. Checking whether I'm connected or not is not a problem, but I don't know how to dismiss every presenting view controller to take me back to the root view (which happens to be the login view).
My view controllers are presented modally, but I also have two navigation controllers with table views including push segues.
Any help with this problem is highly appreciated. Thanks in advance!
The jarring way to do it is just replace the rootViewController with a new login view controller.
- (void)applicationWillEnterForeground:(UIApplication *)application
{
if ([self isDisconnected]) {
self.window.rootViewController = [MyLoginController new];
}
}
For storyboards, assuming your initial storyboard is the login storyboard.
- (void)applicationWillEnterForeground:(UIApplication *)application
{
if ([self isDisconnected]) {
UIStoryboard *storyboard = self.window.rootViewController.storyboard
self.window.rootViewController = [storyboard instantiateInitialViewController];
}
}
Depending on your UX, this may or may not be appropriate.
If all of your content views are presented modally from the root view controller (which doesn't sound like a great idea) then from the app delegate (indeed in applicationWillEnterForeground),
// if we need to login
UIViewController *rootViewController = self.window.rootViewController;
[rootViewController dismissViewControllerAnimated:YES completion:nil];
If the root view controller isn't presenting modally then you need to dismiss in the appropriate way. If any other 'child' view controller is presenting modally then it should dismiss. I guess dismissing when that view controller changes parent view controller will work. You may need to add a notification to inform all view controllers that everything is being torn down.
I am really new to IOS so I apologize if this questions is not worded clearly. I have tried searching around but I have not found exactly what I am looking for.
basically in my AppDelegate applicationDidBecomeActive method, I am making a call to my webservice to make sure that the user is still a valid user, and to pull down some refrehsed data, or kick them back to the login page if they are no longer valid.
The part that I am having trouble with is the second part. How can I load and show and specific ViewController(in this case the loginViewController) when the user is found to be invalid? I want to let the normal viewController flow happen when they are valid, which is is doing fine, but I can not figure out how to launch a specific viewController when I need to from AppDelegate.
Any ideas?
I think I got it! I used this code in the AppDelegate to display the ViewController I needed.
UIViewController *loginController = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:#"LoginViewController"];
UINavigationController *loginNavController = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:#"LoginNavController"];
UIViewController *currentVC = self.window.rootViewController;
_window.rootViewController = loginNavController;
[currentVC presentViewController:loginNavController animated:NO completion:nil];
For simplicity, lets say you have a one view app (not nav controller, not tab bar controller - the solution scales but easier to explain). When you get the appDelegate message that the app launched, then make a UIImageView the root view and show your launch image (user thinks you are still booting up). Try to log in, and do this in some other object (not a view controller). If you succeed, you make your desired view the rootView, and users sees it. If the login fails, then you makea login window the rootView. The key here is to have an object that is driving this and can interact with the appDelegate. You could also add this functionality to the appDelegate itself.