I'm dismissing a modal view controller and then immediately presenting another one, but the latter never happens. Here's the code:
[self dismissModalViewControllerAnimated:YES]; UIImagePickerController *picker = [[UIImagePickerController alloc] init]; picker.delegate = self; picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum; [self presentModalViewController:picker animated:YES];
The first modal VC slides down, but the new picker
never comes up. Any idea as to what's going on?
Aug 2012 Update:
iOS 5 and greater have introduced safer APIs for doing things after modals have animated into / out of place using completion blocks:
[self presentViewController:myModalVC animated:YES completion:^{}];
[self dismissViewControllerAnimated:YES completion:^{}];
Pre-Aug 2012 Answer:
I encountered a similar problem when dismissing modal one and then presenting modal two in rapid succession. Sometimes modal two would show after the modal one was dismissed and sometimes modal two wouldn't appear at all and that made me very sad.
Putting a 1+ second delay on the caller of the method that presented modal two, showModalTwo
, made modal two appear every time after modal one was dismissed:
- (void)didDismissModalOne {
[self performSelector:@selector(showModalTwo:)
withObject:someNumber
afterDelay:1.0f];
}
This confirmed a suspicion that there was some sort of race condition between the dismissal of modal one and the presentation of modal two. Putting a delay on the caller, however, is inelegant and did not guarantee that the race condition wouldn't re-appear under other circumstances.
Turns out that UIViewController
s have a public property, modalViewController
, that gets set up when presentModalViewController:animated:
is called and torn down when dismissModalViewControllerAnimated:
is called. The catch is that it doesn't get torn down synchronously, so it's possible to create a race between removing the old value of modalViewController
and setting up a new value in the following way.
myViewController.modalViewController
now points to modal onemyViewController.modalViewController
has started, but myViewController.modalViewController
the still points to modal onemyViewController.modalViewController]
now points to modal twomyViewController.modalViewController
to nil
, this interrupts the process of modal two animating in and the result is the user never sees it.The race starts on step 2 and manifests on step 4.
My solution was to put a guard condition on the method that presented modal two to ensure that myViewControoler.modalViewController
was nil
before attempting to present modal two.
-(void)showModalTwo:(NSNumber *)aParameter {
if (self.modalViewController) {
[self performSelector:@selector(showModalTwo:)
withObject:aParameter
afterDelay:0.1f];
return;
}
// You can now present the second modal safely.
}
Worked like a charm. A more elegant solution might include a timeout.
I really didn't like the polling aspect of this solution. @Nimrod suggests, in the accepted answer to this question, that you can safely initiate the presentation of modal two from the viewDidDisappear:
method of modal one. I liked the sound of this event driven approach, but after doing a full implementation in my use case I confirmed that the race condition persisted when presenting modal two using a callback inside viewDidDisappear:
. The only way to be absolutely sure that modal two will be presented is to poll inside the parent view controller until you're absolutely sure that self.modalViewController
is nil
. Then and only then is it "safe" to pop modal two.