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:
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?
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()
}
}
}
}
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();
}
}];
}];
}
}
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
}
}
来源:https://stackoverflow.com/questions/41827981/dismiss-and-present-modal-view-controller-with-one-animation