iOS: Handle invalid session in App Delegate - ios

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.

Related

How to change root controller after a server call on App Delegate?

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.

How to reload rootViewController from appDelegate

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.

Returning to the root view controller

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.

How to release memory from UIViewController after popping naviagtion

In my app, I am jumping navigation a bit, here is a rough outline of what my navigation does
Login -> Main Screen -> Settings
From settings, I want a logout that goes back to the login page. I do this like this:
UIViewController* requireController = [[[self navigationController] viewControllers] objectAtIndex:0];
[[self navigationController] popToViewController:requireController animated:YES];
This does return me to the login page. However, I would like to release some things in the Main Screen, for example I have a timer that runs a task every 10 seconds, which continues to run (I can see it in the logs). viewDidUnload obviously no longer gets called as of iOS6. viewDidDisapear also is not quite right because that will also get called when going into settings.
What should I do here to get rid of some tasks in my main view controller?
You can use the popToRootViewControllerAnimated: method to directly jump to the root view controller, in your case the Login controller. If you are not retaining the Main Screen or Settings controllers anywhere in your code, then moving to root view controller will invoke the dealloc method on the view controllers getting removed from navigation stack.
You can override the dealloc in your main screen controller and release the resources. Remember, if you are using ARC do not make a call to [super dealloc] as it will give error. But if you are managing memory manually make sure you add the call at the end of dealloc.
Hope that helps!
Set up a protocol on your logout view controller. Before you pop back to the main, call some method to reset the state of that view controller using the protocol (delegate) you created. On the main VC implement that method. AFTER telling the delegate to do whatever, then pop the login VC.
Here are the basics of this pattern.
http://iosdevelopertips.com/objective-c/the-basics-of-protocols-and-delegates.html
I would either use NSNotificationCenter or use the - (void)viewDidDisappear:(BOOL)animated method of UIViewController.

Stacked UINavigationController

I'm new in iOS and I'm working with Storyboards.
I have an application with some views.
My rootViewController (1) is a UINavigationController that connects to other views. At one point in the application (2), I include a component (SWRevealViewController, Facebook Side Menu Style) that is an instance of UINavigationController, so I have two UINavigationControllers nested within each other. I want to remove or change the first UINavigationController by the new one (2), and just have only one. All views are accessed via custom segues.
Detailed Image Here
I think the solution is to change the rootViewController before loading the view (2), and set the second UINavigationController as the main of the application.
Detailed Image Here
I tried to solve it accessing by:
[UIApplication delegate].window.rootViewController = myController;
but I only have nil or a empty window.
I read many post that the solution could be in my AppDelegate in the method
- (void) applicationDidFinishLaunching: (UIApplication *) application I can't understand how to apply it to my structure, because that method is called when the application is launched.
I think that my workflow application is wrong.Any feedback or help is welcome!
Thanks in advance.
It's fine to change the root view controller from another controller, but your code is wrong. It should be:
[UIApplication sharedApplication].delegate.window.rootViewController = myController;
If you're doing this action from a controller whose view is currently on screen, you can do it this way instead:
self.view.window.rootViewController = myController;
This should work as long as myController has been properly instantiated.
You could possibly remove (1) or off load it into another view controller that is hidden and once the user goes back to a point where you want (1) back you can load it back in. This could be done in the - (void) applicationDidFinishLaunching: (UIApplication *) application.

Resources