Search code examples
objective-cuikituiviewanimationtransition

Unwind from custom modal transition


I use custom transitions for modal segue, implemented using UIViewControllerAnimatedTransitioning delegate. All segues setup via Storyboard.

I have two nav controllers, both have single root VC setup. Let's call them 1st NVC, 1st VC, 2nd NVC and 2nd VC.

1st VC adopts the UIViewControllerAnimatedTransitioning protocol, implementation simply returns animator:

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    return [ModalTransitionAnimator new];
}

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
    return [ModalTransitionAnimator new];
}

When any segue happens from 1st VC -> 2nd NVC, 1st VC catches the event in prepareForSegue: and assigns transitioningDelegate along with modalPresentationStyle because you cannot do that from IB:

UINavigationController* destination = (UINavigationController*)segue.destinationViewController;
destination.transitioningDelegate = self;
destination.modalPresentationStyle = UIModalPresentationCustom;

So everything seems fine, I get a very cool animation running, destination controller shows up, all viewWillAppear: and viewDidAppear: fire in the right order.

And so now on screen, I have 2nd NVC with 2nd VC inside.

I tap "Cancel" button in Navigation bar, unwind action triggers on 1st VC, but no backward animation happening. I mean in fact 2nd VC is still on screen.

So apparently nobody calls dismissViewControllerAnimated. I suppose Storyboard is confused and can't find the way back because 2nd NVC was initially modally presented and then some other controller requests unwinding. How do you deal with that normally?


Solution

  • I followed the unwind chain and found that UIKit was not able to find the right unwind segue after state restoration. Before state restoration everything worked perfectly fine.

    I fixed it manually and traversed my controllers hierarchy to return a controller that has unwind action defined:

    - (UIViewController*)viewControllerForUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender {
        if([NSStringFromSelector(action) hasPrefix:@"unwindToInitialViewController"]) {
            return [[((UINavigationController*)self.centerViewController) viewControllers] firstObject];
        }
    
        UIViewController* c = [super viewControllerForUnwindSegueAction:action fromViewController:fromViewController withSender:sender];
        DDLogInfo(@"%s = %@, c = %@", __PRETTY_FUNCTION__, NSStringFromSelector(action), c);
    
        return c;
    }