When I call dismissViewControllerAnimated:completion: to dismiss a UIViewController the completion block is never executed when the corresponding view is in the middle of being animated onto the screen (using presentViewController:animated:completion:).
The UIViewController does not even dissappear. It is like dismissViewControllerAnimated:completion: is being ignored.
The following code is a simplified code example because the original is much bigger. The code I have given below simulates a use-case where a network communication error might trigger a view to popup whilst another view is also being popped-up at the same time..
Code example:
NSLog(#"Presenting view");
[self presentViewController:changeLocationViewController animated:YES completion:^{
NSLog(#"View done presenting");
}];
NSLog(#"Dismissing view");
[self dismissViewControllerAnimated:NO completion:^{
NSLog(#"View done dismissing");
}];
Log output is:
2013-08-28 16:14:12.162 [1708:c07] Presenting view
2013-08-28 16:14:12.178 [1708:c07] Dismissing view
2013-08-28 16:14:12.583 [1708:c07] View done presenting
Does anyone know how to dismiss the UIViewController in these circumstances?
Thanks in advance.
The reason this code snippet isn't working is because the completion block in these methods are executed at a later time after the animations have completed. You can see this in your logs: "Dismissing view" happens before "View done presenting". Try this instead:
NSLog(#"Presenting view");
[self presentViewController:changeLocationViewController animated:YES completion:^{
NSLog(#"View done presenting");
NSLog(#"Dismissing view");
[self dismissViewControllerAnimated:NO completion:^{
NSLog(#"View done dismissing");
}];
}];
EDIT:
If you need to make sure the view is dismissed when the network error happens, try setting a boolean instance variable called networkErrorFound.
When you finish the network connection, set this to YES if an error happens. Then use this code:
[self presentViewController:changeLocationViewController animated:YES completion:^{
NSLog(#"View done presenting");
NSLog(#"Dismissing view");
if (self.networkErrorFound) {
[self dismissViewControllerAnimated:NO completion:^{
NSLog(#"View done dismissing");
}];
}
}];
That way, it'll wait until it's done presenting to dismiss. You would also need to handle the case that the error happens after the animation is done (for instance, a slow connection that eventually fails), but that's outside the scope of this question.
Why dont you dismiss it when its done loading?
[self presentViewController:changeLocationViewController animated:YES completion:^{
NSLog(#"View done presenting");
NSLog(#"Dismissing view");
[self dismissViewControllerAnimated:NO completion:^{
NSLog(#"View done dismissing");
}];
}];
OK. It seems like you want to present a VC, but if there is no network found, close the VC. The only reason that I can think of needing to do it this way is if the network only gets checked in the new VC that you are presenting (and want to dismiss if it fails to connect).
And you would be able to achieve that by implementing the code shown in the answer given by #aopsfan.
But think about that UI flow. You are telling a starving man (the user) he can have a sandwich (the next VC that he wants to see)... But WAIT! (dismiss the wanted VC) No, you can't have the sandwich (no network)! Fooled you!.
The way to do it to keep the UI flow nice and not aggravating, would be to check for network connection before presenting the VC. Probably check for network in the IBAction (?) that you use to present the new VC. That way, you can check before presenting. Instead of present-cancel
Heck, you could even show an HUD "in progress" View to let the user know what happening!
Related
I have a view controller(VC1) embedded in a navigation controller(NAV1). In its viewWillAppear method, I make a call to modally present another view controller. In one case I need the new view controller(VC2) to be presented with animation, and in another case it should be presented without animation. VC2 is also embedded in its own navigation controller(NAV2).
All is fine when the animation flag is set to TRUE. When I set the flag to FALSE, couple of things go wrong:
1. I get the following warning in the console: Presenting view controllers on detached view controllers is discouraged
2. When I move back from VC2 after calling dismissViewControllerAnimated:FALSE completion:nil the viewWillAppear method of VC1 does not get called. It gets called if the animation flag is set to TRUE.
In VC1:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self performSelector:#selector(importStuff:)
withObject:nil];
}
-(void)importStuff
{
//instatntiate VC2
//instantiate NAV2
[Utility presentViewController:pNavController
fromViewController:self
animated:FALSE
completion:nil];
// the above method calls the presentViewController:animated:completion: method
}
IN VC2:
[Utility dismissViewController:self
animated:FALSE
completion:nil];
The above method calls the dismissViewControllerAnimated: method.
Not a solution but a workaround:
You can easily postpone any UI operation to the very next event loop by using afterDelay:0:
[self performSelector:#selector(importStuff:)
withObject:nil
afterDelay:0];
This will give a chance to the current operation to complete.
Furthermore, a delay of 0.4 will match the OS. However, whatever delay you use (other than 0) is a kludge and not guaranteed to work under every situation, device and memory load, etc.
Instead, you should revisit your approach.
Change your design:
Do not run the risk of encountering this animation race in the first place.
You have a couple of options, including:
Controlling the transition animation yourself and waiting for its completion prior pushing another view controller (using a completion signal or completion block)
Changing your methodology entirely to avoid this conundrum altogether
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self performSelector:#selector(importStuff) withObject:nil afterDelay:0.1];
}
-(void)importStuff
{
//instatntiate VC2
//instantiate NAV2
SecondViewController *viewMe=[self.storyboard instantiateViewControllerWithIdentifier:#"second"];
[self.view.window.rootViewController presentViewController:viewMe animated:NO completion:nil];
// the above method calls the presentViewController:animated:completion: method
}
I get that error in the debug log every time I run my app. I click Find Game, it takes me to main screen, the keyboard doesn't work due to this error and when I hit the back button, the view controller it just pops back up (again due to this error). I can't figure out how to fix it so any help would be greatly appreciated. Thanks.
// A peer-to-peer match has been found, the game should start
- (void)turnBasedMatchmakerViewController: (GKTurnBasedMatchmakerViewController *)viewController didFindMatch:(GKTurnBasedMatch *)match
{
// Display default view [presentingViewController dismissViewControllerAnimated:YES completion:nil];
[presentingViewController dismissViewControllerAnimated:YES completion:^{
// Present next controller here
[presentingViewController performSegueWithIdentifier:#"GamePlayScene" sender:match];
}];
// Removing line below fixes Warning: Attempt to dismiss from view controller <GameNavigationController: 0x78f4f820> while a presentation or dismiss is in progress!
// [self dismissModalViewController];
The GameKit documentation states that your didFindMatch method should dismiss the view controller and perform needed actions on the match object.
Your method should look like this.
- (void)turnBasedMatchmakerViewController: (GKTurnBasedMatchmakerViewController *)viewController didFindMatch:(GKTurnBasedMatch *)match {
// Dismiss the view controller
[viewController dismissViewControllerAnimated:YES completion:nil];
// Perform your logic
}
From GameKit documentation:
Your game should dismiss the view controller and use the match object
to show the current state of the match to the player.
I am getting this warning, and I am not sure why. I tried getting rid of it by adding the subview but that didn't solve my problem either. Help please
Warning: Attempt to present on whose view is not in the window hierarchy!
Here is the code I am using on one VC:
[self dismissViewControllerAnimated:YES completion:^{
[self presentViewController:rootViewContoller animated:YES completion:nil];
}];
Edit: my app still works properly in regards to presenting view controllers I just want to know why the compiler is generating this warning
The warning shows up because you are presenting a new view controller (rootViewController) an another view controller that you actually just dismissed!
[self presentViewController:rootViewContoller animated:YES completion:nil]; is in the completion handler block of the dismissal of the other view controller, that means it gets called after that view controller has been dismissed.
You can use this:
[self dismissViewControllerAnimated:YES completion:^{
[self.presentingViewController presentViewController:rootViewContoller animated:YES completion:nil];
}];
I try 2 ways to dismissed 2 viewcontrollers consecutively but only one of them got dismissed not the second one
method1
-(void) LoginDone:(NSNotification *)notif
{
[self dismissViewControllerAnimated:YES completion:NULL]; //previous viewcontroller
[self dismissViewControllerAnimated:YES completion:NULL]; //current viewcontroller
}
method2
-(void) LoginDone:(NSNotification *)notif
{
[self dismissViewControllerAnimated:YES completion:NULL];
[[NSNotificationCenter defaultCenter] postNotificationName:#"LoginDone2" object:nil];
}
-(void) LoginDone2:(NSNotification *)notif
{
[self dismissViewControllerAnimated:YES completion:NULL];
}
I need to find out a way to dismiss both the previous viewcontroller and current viewcontroller consecutively.
This is now an old question, but it seems to be exactly the problem I am having presently.
Here what I did:
[self.presentingViewController.presentingViewController
dismissViewControllerAnimated:YES completion:nil];
And it works for me. I hope it can be useful to someone.
By calling
[self dismissViewControllerAnimated:YES completion:NULL];
you are telling self to dismiss the view it presented. Telling twice the same self object to dismiss the view it presented, will not change the result. In other words self cannot represent the "current view" and the "previous view" at the same time as per your comment to the code. self is just a single controller representing a single view, either the current or the previous one.
To fix this, you should send the dismissViewControllerAnimated to self (that presented the top-most view, I assume) and to the other view controller object that presented the previous view.
In other words, I would expect something like this:
-(void) LoginDone:(NSNotification *)notif
{
[self dismissViewControllerAnimated:YES completion:NULL];
[self.previousController dismissViewControllerAnimated:YES completion:NULL];
}
Actually, you could send just one message to the second view controller and both views would be dismissed (source):
If you present several view controllers in succession, and thus build a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack.
I know this is an old question but maybe somebody will look for solution on this issue so here it is:
-(void) closeModalViews
{
[previousVC dismissViewControllerAnimated:YES completion:^(void) {
[self dismissViewControllerAnimated:YES];
}];
}
I like JPetric's idea, but first you must dismiss the current view controller's view and only then can you dismiss the presenting view controller's view.
[self dismissViewControllerAnimated:NO completion:^(void) {
[self.presentingViewController dismissViewControllerAnimated:NO completion:nil];
}];
As far as I could understand you are trying something like below:
There are 2 view controllers.
You want both of them to be vanished.
Another controller comes in.
But the fact is only one controller is displayed, why you would need to dismiss 2 then?
You can use self.view.hidden=true; on the jumped viewcontrollers while animating back to the first viewcontroller. Using [self.presentingViewController dismiss...] is not working for me without hiding.
I recently encountered a hair-pulling situation in my iOS app, where I was trying to successively dismiss one presented UIViewController from my window's rootViewController, using:
[rootViewController dismissViewControllerAnimated:YES completion:NULL]
and present another one shortly thereafter (in another method, incidentally), with:
UIViewController *vc2 = [[[MyViewController2 alloc] initWithNibName:nil bundle:nil] autorelease];
[rootViewController presentViewController:vc2 animated:YES completion:NULL];
Problem was, I could never get the second view controller to show up. Turns out, as near as I can tell, dismissViewControllerAnimated:completion: needs that asynchronous block of "completion" time to pass, before presentViewController:animated:completion: will work properly again. This fact is not directly documented in Apple's docs, from what I can tell.
The solution I came up with was to wrap the dismissal with a method that specifies the selector you would want to call afterwards, like so:
- (void)dismissViewController:(UIViewController *)presentingController
postAction:(SEL)postDismissalAction
{
[presentingController dismissViewControllerAnimated:YES
completion:^{
[self performSelectorOnMainThread:postDismissalAction
withObject:nil
waitUntilDone:NO];
}];
}
And then I would call:
[self dismissViewController:self.window.rootViewController
postAction:#selector(methodForNextModalPresentation)];
Anyway, I wanted to post, as I looked around and hadn't seen anyone with this particular problem, so I thought it might be useful for people to understand. And also, I wanted to verify that I'm not hacking a solution that has a better design pattern for resolution.
Just for the sake of clarity. are you saying that this code doesn't work?
[myRootViewController dismissViewControllerAnimated:YES completion:^{
[myRootViewController pushViewController:newController animated:YES];
}];