I am coding a game where the user can only play if there is an active internet connection. I want to dismiss all ViewControllers and get back to the login screen if the internet status changes. Here's the hierarchy:
My App Delegate show the LoginViewController by:
self.window.rootViewController = loginViewController;
[self.window makeKeyAndVisible];
Here the user logs in. While logging in I check for connectivity and handle errors. If login is successful loginViewController presents mainViewController using
[self presentModalViewController:self.mainViewController animated:YES];
Then mainViewController presents other controllers, which can present other controllers and so on. The present and dismiss routine is working properly.
Now, if Reachability status changes, I want to display an alert and force the app to get back to the login screen. I know how to listen for these notification and take actions appropriately. It is well documented :)
How do I dismiss all view controllers until I land back in the login screen? Do I need to listen to the notification in all view controllers and dismiss them separately? Is there any way of catching the notification in a parent (like the app delegate) and dismissing all view controllers from there (without holding a reference to all of them beforehand)?
Obviously, I'm not using a navigationController so
[self.navigationController popToRootViewControllerAnimated:NO];
is not possible.
Edit: Alternately, I can dismiss and release everything and start from scratch, like if the application is started new. Is this a better approach? How exactly do I do it?
Thank you.
OK I solved it. Simply calling
[self dismissModalViewControllerAnimated:NO];
on the loginViewController dismisses main view controller and all controllers above on the stack.
Related
I have a a VC structure like this
UIPageViewController -> detailViewController -> popoverviewcontroller
The popoverviewcontroller is dismissed using an unwind segue, bringing us back to the detailviewcontroller
Now, after the popover is done being dismissed, I would like to refresh the pages on the pagecontroller, since the action the user takes has changed the data.
I would also like to display an alert notifying the user about whether they were successful.
So I tried putting this code in the pageViewcontroller
- (IBAction) unwindFromPopup:(UIStoryboardSegue*)unwindSegue{
[self refreshPages];
UIAlertController * alertController = [UIAlertController alertControllerWithTitle:#"alert" message:#"this should appear" preferredStyle:UIAlertControllerStyleAlert];
[self presentViewController:alertController animated:YES completion:nil];
}
I tried moving the above code to the detail view controller instead, but I am getting no result from this. No error or anything, just a complete lack of alert. I put a breakpoint in the segue, and the code gets called. But no alert.
I thought of putting the code in one of the respective viewDidAppear methods, but for some reason viewDidAppear does not get called for either the pageviewcontroller or the detailview controller after I dismiss the popup.
So at this point I have no idea how to make this alert appear.
Do I need to post my full code, or is my problem apparent with the details I've included?
Thanks - based on your comment ... long ago in a distant version of iOS I performed all the possible segues and noted what gets called when and have a table of that that I based my answer on. I must admit, nowadays I get most done using the presentation controller delegate.
Anyhow, to reply to your question, when you pop or modally present a controller, the controller that is being presented will message beingPresented and beingDismissed when it is done and you might be able to use this for what you are trying to do.
When you push a controller it will message isMovingToParentViewController when shown and isMovingFromParentViewController when dismissed, again in the controller being presented.
Back to a pop ... it will message prepareForSegue in the presenting VC and viewWillAppear and viewDidAppear in the presented VC and, when dismissing, will message only viewWillDisappear and viewDidDisappear in the presented VC, thus your problem. At least it will also message beingDismissed as mentioned and if you can use that I am really glad for you.
I'm currently working on an app that builds a stack of controllers depending on the user.
Basically, I have a UIViewController that has a UIButton leading to another UIView Controller; that has a button leading to another view controller and so on. The view controllers are pushed so that when the user always press the button, I get a stack of multiple view controllers. The views are popped whenever the user wants to go back to the previous view controller.
Everything is working well (push and pop). However, at random instances, the app would crash. I noticed that it happens when there are already a large amount of views pushed, and I suspect that it can be a memory issue.
My question is, other than pushing the view controllers, is there an alternative so that I can avoid stacked views? Could it also be that the crash is not because of the stacked views but because I'm just missing something out? There is no error presented in the logs so I can't find out what's happening and I'm also new to iOS development.
Thank you very much!
Edit 1: There is no error in the logs but when the app crashes, there is this message:
Thread 1: EXC_BAD_ACCESS(code = 1, address = 0xd000000c)
Edit 2: This is how I am pushing the controller:
CustomController *custom = [self.storyboard instantiateViewControllerWithIdentifier:#"Custom"];
[self.navigationController pushViewController:custom animated:YES];
And this is how I popped it when the back button is pressed:
[self.navigationController popViewControllerAnimated:YES];
Edit 3: After enabling zombie objects in the scheme, I started to get this messages after multiple push and pop:
nested push animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
Unbalanced calls to begin/end appearance transitions for
Do those messages say that the problem is actually on pushing the controller with animations? Thanks everyone!
Edit 4: I'll try to revise the question to make it more descriptive
This is my setup:
Controller A displays icons that corresponds to different places. You can click on the icon to push Controller B and display details for Location A.
Controller B displays information about Location A, with a button to show Controller A that now displays icons close to location of Location A. Now, you can again click an icon, say for Location B, and display details and so on.
When the user presses the back button, it should display the previous view controller. This is why I used push and pop. Is there a better way to handle this process. Thanks again!
My suggestion is: check if Zombie Objects is enabled and use the instrument "Allocations" to see if your application have, in fact, memory issues. With the informations provided by these tools, you can figure out what is happening with your application and work on it.
Tell me if you need help.
Good luck!
When push or pop, you should turn off animation. I think this causes crash when animation does not finish.
Push: self.navigationController pushViewController:custom animated:NO];
Pop: [self.navigationController popViewControllerAnimated:NO];
My guess is that you push multiple view controllers with animations - this may be a root cause of error. If you push more than one view controller you should animate only LAST push, say:
VC1 *vc1 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC1"];
[self.navigationController pushViewController:vc1 animated:NO];
VC2 *vc2 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC2"];
[self.navigationController pushViewController:vc1 animated:NO];
VC3 *vc3 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC3"];
[self.navigationController pushViewController:vc1 animated:YES];
However, i hardly imagine the situation where the multiple push would be necessary - i think it always leads to bad UX.
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.
When my App is starting I have a modal view controller to enter credentials (IP#, username, password...). When the user is logged he can open many UIViewControllers that can open others UIViewController and so on... These view controller can be navigation controller, tab bars, modals....
Next, the user can leave the application in background. Next, user can open the Mail App and open a mail that contains an attachment to navigate to my App. When the user select my App to open the attachment.
When the App moves in foreground I need to go to a specific viewController of the hierarchy (the first ViewController opened after the login screen).
To move the App to this first ViewController I have tried to use
– dismissViewControllerAnimated:completion:
but without success, there're still a view from the hierarchy that is still displayed.
Any idea how to do this?
Regards,
Sebastien.
If all the view controllers are in 1 Navigation controller and pushed onto the stack, you can simply use either popToRootViewControllerAnimated: or popToViewController:animated:
Otherwise, why not dismiss the first Modal view controller, then put it back with the view controller you want.
I found a solution with this small piece of code
UINavigationController* navc = (UINavigationController*)viewController.topViewController;
if (navc != Nil) {
if (navc.presentedViewController != Nil) {
[navc dismissViewControllerAnimated:FALSE completion:Nil];
}
[navc popToRootViewControllerAnimated:TRUE];
}
It was not complex at the end!
Sébastien.
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.