Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Visual artifact when dismissing more than one modal view controller at once

I've been struggling to find an answer to this question. I build a stack of modals via:

[[[NavA viewControllers] objectAtIndex:0] presentViewController:NavB animated:YES completion:NULL];
[[[NavB viewControllers] objectAtIndex:0] presentViewController:NavC animated:YES completion:NULL];

When I want to dismiss the NavA and NavB modals simultaneously I call

[[[NavA viewControllers] objectAtIndex:0] dismissViewControllerAnimated:YES completion:NULL];

This works fine except there's a brief flash where you can see NavB as the full stack is dismissed.

I stepped through the debugger and it looks like before the animation begins NavC disappears instantly and NavB dismisses with animation.

Is there any way to avoid this visual artifact and have the whole stack dismiss smoothly with NavC visible for the full duration of the animation?

Edit: To clarify, I'm presenting UINavigationController rather than UIViewController because this flow is for user login and has multiple possible branches that can lead back either to the current stage e.g. NavC (LoginPage), NavB (LandingPage with login and signup buttons) or all the way back to the root, NavA (main page of the application). In the iOS documentation they present a similar design pattern with the camera where each stage presents a UINavigationController with multiple possible view controllers https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html

like image 280
alexgophermix Avatar asked Jul 02 '15 22:07

alexgophermix


2 Answers

Actually there's no way to do it by just using dismissViewControllerAnimated:completion: method no matter where you put it or how you call it (at least I couldn't, if someone knows a way - we all want to know).


HOWEVER, there's a hack you can use to achieve your desired outcome (this code should be called from "B" ViewController):

// Snapshot of "C" ViewController
UIGraphicsBeginImageContextWithOptions([UIScreen mainScreen].bounds.size, YES, 0);
UIView *snapshot = [self.presentedViewController.view snapshotViewAfterScreenUpdates:NO];
UIGraphicsEndImageContext();

// Cover the entire view of "B" (and hide navigation bar)
[self.view addSubview:snapshot];
self.navigationController.navigationBarHidden = YES;

// Dismiss "C" without animation
[self.presentedViewController dismissViewControllerAnimated:NO completion:^{
    // Dismiss "B" with animation
    [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}];
like image 182
Islam Avatar answered Oct 24 '22 21:10

Islam


If you are using a Storyboard then this should be achievable using Unwind Segues. Mike Woelmer has a good set of articles about this. Basically you provide information to the Storyboard about how a view can unwind through several different views to get a to a view that's already on the stack.

However, I'm a bit confused in the same way that Jeffery Thomas is in the comments: why are you presenting a navigation controller with another navigation controller? I can understand that you might want the navigation bar to look different on different views, but you can customise that when the view is due to appear. You should think a bit about the content of the views in NavB and NavC and ask yourself whether they are supposed to be presented as modal views or whether they would be better off as part of a single navigation stack. By presenting each Navigation Controller modally you're ending up with multiple navigation stacks, not a single stack with multiple view controllers. Even if just NavB and NavC were part of the same stack it would probably remove the visual glitch you're seeing.

If you did use a single navigation controller then you can get back to a previous view controller in the navigation stack by using the method -popToViewController:animated: on UINavigationController.

If you decide that presenting NavB and NavC modally as you are currently doing is the right thing to do then you are likely to get into trouble because when you ask NavA to dismiss its view controller it will try to dismiss NavB, which to it means setting up a transition between the NavB's view and NavA's view. That's why you're seeing that transition, and not the one you want (which is between NavC's view and NavA's). One way which might work (and sounds a bit weird) is to try to present NavA from NavC, then override the transition to make it look like you're popping NavC off the stack. Once you're there you can clean things up by removing any strong references to NavB and NavC. This article from Ash Furrow will get you most of the way.

like image 22
quantumkid Avatar answered Oct 24 '22 21:10

quantumkid