Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dismiss and present modal view controller with one animation

The problem is that I don't know how to dismiss and present a view controller with only one transition animation.

My storyboard structure is:

enter image description here We can say that A controller is what follows the NavigationController, B is the Startup reference and C is the TabBar ViewController. Both B and C are presented modally with a Cross Dissolve transition.

When a user logins into the app (from B), C controller is presented modally with a Flip horizontal transition. And when a user logouts (from C), B is presented in the same way. On A controller I perform a direct segue to B or C depending on if user is logged or not.

My problem is that if I don't dismiss previous view controller from B or C, that controller is leaked. On the contrary if I dismiss it, A is showed before the target controller (B or C) is presented.

Is it possible to show only the Flip Horizontal transition and step over A view?

like image 711
kikettas Avatar asked Jan 24 '17 12:01

kikettas


People also ask

How do you dismiss the presenting view controller?

When it comes time to dismiss a presented view controller, the preferred approach is to let the presenting view controller dismiss it. In other words, whenever possible, the same view controller that presented the view controller should also take responsibility for dismissing it.


3 Answers

My solution to this issue was replacing current rootViewController, supporting different transitions:

static func replaceRootViewController(with viewController: UIViewController, transition: UIViewAnimationOptions, completion: (() -> ())? = nil) {        
    if transition == .transitionCrossDissolve {
        let overlayView = UIScreen.main.snapshotView(afterScreenUpdates: false)
        viewController.view.addSubview(overlayView)
        UIApplication.shared.keyWindow?.rootViewController = viewController

        UIView.animate(withDuration: 0.65, delay: 0, options: transition, animations: {
            overlayView.alpha = 0
        }, completion: { finished in
            overlayView.removeFromSuperview()
            if let completion = completion{
                completion()
            }
        })
    } else {
        _ = viewController.view
        UIView.transition(with: UIApplication.shared.keyWindow!, duration: 0.65,options: transition, animations: {
            UIApplication.shared.keyWindow?.rootViewController = viewController
        }){_ in
            if let completion = completion {
                completion()
            }

        }
    }
}
like image 88
kikettas Avatar answered Oct 16 '22 09:10

kikettas


Here's a solution I've used for this problem. I've no idea how it integrates with Storyboards since I don't use those.

Added this method in a category to UIViewController, then can invoke anywhere previously called presentViewController:animated:completion. Results in a seamless animation of the new controller while still dismissing the previous one.

-(void)presentViewControllerDismissingPrevious:(UIViewController* _Nonnull)controller animated:(BOOL)animated completion:(void (^ __nullable)(void))completion {

    UIViewController* visibleController = self;
    {
        UIViewController* temp;
        while( ( temp = visibleController.presentedViewController ) != nil ) {
            visibleController = temp;
        }
    }

    if( visibleController == self ) {
        // no previous controller to dismiss
        [self presentViewController:controller animated:animated completion:completion];
    } else {
        // create a temporary snapshot of the visible controller's entire window
        // and add to the current view controller's window until animation completed
        UIWindow* visibleWindow = visibleController.view.window;
        UIView* tempView = [visibleWindow snapshotViewAfterScreenUpdates:NO];
        UIView* rootView = self.view.window.subviews[0];
        tempView.frame = [rootView convertRect:visibleWindow.bounds fromView:visibleWindow];
        [rootView addSubview:tempView];

        [self dismissViewControllerAnimated:NO completion:^(){
            [self presentViewController:controller animated:animated completion:^(){
                [tempView removeFromSuperview];
                if( completion ) {
                    completion();
                }
            }];
        }];
    }
}
like image 2
zeroimpl Avatar answered Oct 16 '22 09:10

zeroimpl


The both answers were very helpful, here is the Swift 4 version:

func presentHidingBehindScreenSnapshot(viewController: UIViewController,
                                     completion: (() -> (Void))? ) {
if let screenSnapshot = UIApplication.shared.keyWindow?.snapshotView(afterScreenUpdates: false),
  let rootViewController = UIApplication.shared.keyWindow?.rootViewController {
  rootViewController.view.addSubview(screenSnapshot)
  rootViewController.view.bringSubview(toFront: screenSnapshot)

  rootViewController.dismiss(animated: false, completion: {
    rootViewController.present(viewController, animated: false, completion: {
      screenSnapshot.removeFromSuperview()
      if let existingCompletion = completion {
        existingCompletion()
      }
    })
  })
} else {
  #if DEBUG
  fatalError("Can't hide behind snapshot while presenting other view controller")
  #endif
}

}

like image 2
Alexander Stepanishin Avatar answered Oct 16 '22 10:10

Alexander Stepanishin