Dismissing multiple modal view controllers at once?

Be sure that you're only calling dismissModalViewControllerAnimated: once.

I have found that asking to dismiss each stacked modal view controller will cause both of them to animate.

You have: A =modal> B =modal> C

You should only call [myViewControllerA dismissModalViewControllerAnimated:YES]

If you use [myViewControllerB dismissModalViewControllerAnimated:YES], it will dismiss C, and not B. In normal (unstacked) use, it would dismiss B (due to the responder chain bubbling the message up to A). In the stacked scenario that you describe B is a parent view controller and this takes precedence over being a modal view controller.


Although the accepted answer did work for me, it may be outdated now and left a weird looking animation where the topmost modal would immediately disappear and the animation would be on the rear modalview. I tried many things to avoid this and ended up having to use a bit of a hack to have it look nice. Note:(only tested in iOS8+, but should work iOS7+)

Basically, viewControllerA creates a UINavigationController with viewControllerB as the rootview and presents it modally.

// ViewControllerA.m
- (void)presentViewB {
    ViewControllerB *viewControllerB = [[ViewControllerB alloc] init];
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewControllerB];

    navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
    [self presentViewController:navigationController animated:YES completion:nil];
}

Now in viewControllerB we are going to present viewControllerC the same way, but after we present it, we are going to put a snapshot of viewControllerC over the view layer on viewControllerB's navigation controller. Then, when viewControllerC disappears during dismissal, we won't see the change and the animation will look beautiful.

//ViewControllerB.m
- (void)presentViewC {
    ViewControllerC *viewControllerC = [[ViewControllerC alloc] init];

    // Custom presenter method to handle setting up dismiss and snapshotting 
    // I use this in a menu that can present many VC's so I centralized this part.
    [self presentViewControllerForModalDismissal:viewControllerC];
}

Below are my helper functions that are used to present the view and handle dismissal. One thing to note, I am using Purelayout for adding auto layout constraints. You can modify this to add them manually or get Purelayout at https://github.com/PureLayout/PureLayout

#pragma mark - Modal Presentation Helper functions
- (void)presentViewControllerForModalDismissal:(UIViewController*)viewControllerToPresent {
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewControllerToPresent];
    navigationController.modalPresentationStyle = UIModalPresentationFormSheet;

    // Ensure that anything we are trying to present with this method has a dismissBlock since I don't want to force everything to inherit from some base class. 
    NSAssert([viewControllerToPresent respondsToSelector:NSSelectorFromString(@"dismissBlock")], @"ViewControllers presented through this function must have a dismissBlock property of type (void)(^)()");
    [viewControllerToPresent setValue:[self getDismissalBlock] forKey:@"dismissBlock"];

    [self presentViewController:navigationController animated:YES completion:^{
        // We want the presented view and this modal menu to dismiss simultaneous. The animation looks weird and immediately becomes the menu again when dismissing.
        // So we are snapshotting the presented view and adding it as a subview so you won't see the menu again when dismissing.
        UIView *snapshot = [navigationController.view snapshotViewAfterScreenUpdates:NO];
        [self.navigationController.view addSubview:snapshot];
        [snapshot autoPinEdgesToSuperviewEdges];
    }];
}

- (void(^)()) getDismissalBlock {
    __weak __typeof(self) weakSelf = self;
    void(^dismissBlock)() = ^{
        __typeof(self) blockSafeSelf = weakSelf;
        [blockSafeSelf.navigationController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    };

    return dismissBlock;
}

Now we just need to ensure we have the dismissBlock defined as a property in ViewControllerC.h (you can obviously replace this whole part with delegate methods or other equally as exciting design patterns, the important part is to handle dismissal at the viewControllerB level)

// ViewControllerC.h
@interface ViewControllerC : UIViewController
@property (nonatomic, copy) void (^dismissBlock)(void);
@end

//ViewControllerC.m
// Make an method to handle dismissal that is called by button press or whatever logic makes sense.
- (void)closeButtonPressed {
    if (_dismissBlock)  {// If the dismissblock property was set, let the block handle dismissing
        _dismissBlock();
        return;
    }

    // Leaving this here simply allows the viewController to be presented modally as the base as well or allow the presenter to handle it with a block.
    [self dismissViewControllerAnimated:YES completion:nil];
}

Hope this helps, happy programming :)