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];
}];
Related
I have a UIViewController subclass called NewsViewController which has a completion block property that is called from a button action. The controller is set up and presented in another view controller like this:
newsViewController.completionBlock = ^{
[self dismissViewControllerAnimated:YES completion:nil];
};
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:newsViewController];
[self presentViewController:navigationController animated:YES completion:nil];
In iOS 10 this all works fine, however in iOS 9 the view is not being dismissed. If I put a breakpoint there it does get hit.
I have tried the following without success:
Called it from the main thread (both synchronously and asynchronously)
I have tried it using GCD like this:
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:nil];
});
I have also tried it by putting the dismissal call into a method and then calling
[self performSelectorOnMainThread:#selector(dismissModalView) withObject:nil waitUntilDone:NO];
I don't actually thing the issue is the thread since a call to [NSThread isMainThread] from within the completion block returns YES.
Calling it with a delay
[self performSelector:#selector(dismissModalView) withObject:nil afterDelay:0.1];
Calling dismiss on another view controller
I have tried calling it on navigationController, self.presentedViewController and self.presentingViewController.
Calling dismiss directly from NewsViewController
In the button action where the completion block was called I called [self dismissViewControllerAnimated:YES completion:nil] directly.
Btw. just for fun I tried calling the dismiss method from the completion block of the presentViewController method and there it did get dismissed.
I have finally located the problem and it was quite unexpected. The thing is that the NewsViewController is presented over my login view controller. This controller allows the user to use Touch ID to log in so it requests the Touch ID prompt in its viewDidAppear method. Apparently, this messes with the dismissal of the presented view, and seemingly only in iOS 9 (well, maybe not only, but it seems to work fine in iOS 10).
Try with below code:
newsViewController.completionBlock = ^{
[self performSelector:#selector(Dismiss) withObject:nil afterDelay:0.3];
};
-(void)Dismiss
{
[self dismissViewControllerAnimated:YES completion:^{
}];
}
As in the title, I do not manage to have that callback called in order to dismiss the game center view controller neither on my iOS 7 iPhone nor iOS 8 iPad. This is the code I use:
GKGameCenterViewController *controller=nil;
- (IBAction)achievementButtonClicked:(id)sender {
if (!controller){
controller=[[GKGameCenterViewController alloc] init];
controller.delegate=self;
}
NSLog(#"controller=%#", controller);
if (controller) [self presentViewController:controller animated:YES completion:nil];
}
-(void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController{
[gameCenterViewController dismissViewControllerAnimated:YES completion:nil];
// I also tried [self dismissViewControllerAnimated:YES completion:nil] but anyway the function seems to not even enter here
}
If I take the function off, the delegate complaints it is missing, so the issue should not be connected to that. What might that be and how to fix it?
My problem is that I used:
controller.delegate=self;
omitting:
controller.gameCenterDelegate = self;
Once inserted the latter, the view controller dismisses without problems, both when I manually present the controller and when it is shown to login her. I really wonder why that beast has too delegates, if not to confuse developers...
Calling Code here within GameListViewController:
[self presentViewController:[[StartNewGameViewController alloc]
initWithNibName:#"StartNewGameViewController" bundle:nil] animated:YES completion:nil];
Callback Code here within StartNewGameViewController:
[(id)self.parentViewController showSetup:m_tableID];
[self dismissViewControllerAnimated:YES completion:^{
[GLVC showSetup:m_tableID];
}];
Also have tried:
[(id)self.GLVC showSetup:m_tableID];
Tried three different methods above here. The first method has no error, bottom method says show Setup has no known class method, third method also no error. First and third have no errors, but are not working.
.h of StartNewGameController
#import "GamelistViewController.h"
#property (nonatomic, assign) GamelistViewController *GLVC;
And I synthesize GLVC on the top of the .m
"showSetup" is a method that works just fine while called from GameListViewController and is declared in GameListViewController.h like so:
-(void)showSetup:(int)tableID;
I've seen some other stack overflow questions related to this one, but none of them have resolved my issue.
[(id)self.parentViewController showSetup:m_tableID];
works because the reference to parentViewController is automatically configured for you.
[GLVC showSetup:m_tableID];
should be _GLVC or, preferably self.GLVC and doesn't work because you never set the reference before you present the view controller. (previously it didn't work because you were using a class name, expecting it to be an instance variable name, due to bad naming, but it wasn't).
[(id)self.GLVC showSetup:m_tableID];
Is really the same as the above (in your new edited code).
The solution is to set GLVC on the created view controller before you call presentViewController:animated:completion:
StartNewGameViewController *svc = [[StartNewGameViewController alloc] initWithNibName:#"StartNewGameViewController" bundle:nil];
svc.GLVC = self;
[self presentViewController:sec animated:YES completion:nil];
This code will do the stuff i guess. presentingViewController will automatically set it for you while presenting the controller.
[self dismissViewControllerAnimated:YES completion:^{
[(id)self.presentingViewController showSetup:m_tableID];
}];
I'm really stumped here.
_vc = [[VLCKitViewControlleriPhone alloc]initWithNibName:#"VLCKitViewControlleriPhone" bundle:nil];
_vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:_vc animated:YES completion:nil];
Gives me EXC_BAD_ACCESS (code = 2, address = 0x0) when the presentViewController method is called. The view controller is NOT nil. It also happens with or without a nib name. If I comment out the presentViewController line, the rest of the code continues fine, including method calls made to the view controller itself. The view controller is running, I just can't see anything because it's not actually showing the view.
I enabled NSZombies and tried it with Instruments running, but it's not showing me anything. The app just quits and instruments stops without giving me any information. Anyone know what the problem might be?
You can try this
if ([controller respondsToSelector:#selector(setModalPresentationStyle:)])
{
[controller setModalPresentationStyle:UIModalPresentationFullScreen];
} else {
[controller setModalPresentationStyle:UIModalPresentationFormSheet];
}
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!