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?
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.
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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With