So I built a custom presenting transition animation and everything seems to be working great except the view controller lifecycle methods are not being called on dismiss.
@John Tracids' anser solved my issue. Thanks John!
But I would like to extend an answer a bit.
If you are presenting UIViewController instance with modalPresentationStyle = .custom
(objc UIModalPresentationCustom) in order to keep viewcontroller's lifecycle methods being called, you have to manage viewcontroller’s appearance explicitly. To do that just call beginAppearanceTransition before animation and endAppearanceTransition at the animation completion block.
Also you can pass to your transitioning animator class custom UIPresentationController subclass with overridden value shouldRemovePresentersView
returning true
without calling beginAppearanceTransition
// Swift 4
put this to your custom UIViewControllerAnimatedTransitioning class before animation
fromViewController.beginAppearanceTransition(false, animated: true)
toViewController.beginAppearanceTransition(true, animated: true)
UIView.animate(withDuration: animationDuration, animations: {
// animation logic…
}) { finished in
fromViewController.endAppearanceTransition()
toViewController.endAppearanceTransition()
let transitionSuccess = !transitionContext.transitionWasCancelled
transitionContext.completeTransition(transitionSuccess)
}
// UIPresentationController subclass
class PresentationController: UIPresentationController {
override var shouldRemovePresentersView: Bool { return true }
}
If you are using UIModalPresentationCustom
you should provide custom UIPresentationController
class, and if you want to use ViewController lifecycle callers, you need to override shouldRemovePresentersView
and return YES
.
If you would like to keep presenters and still have ViewControlelr lifecycle callback, you can override private method _shouldDisablePresentersAppearanceCallbacks
and return NO
in your custom UIPresentationController
class.
Ah, this is a modal presentation. I don't believe viewWillAppear and viewDidAppear are called with custom transition using the method, as the view is technically still active in the view hierarchy. I'd suggest using delegation here as you normally would with a presented modal.
Create delegate protocol on the presented VC. Create a delegate method that can be called from the presenting VC. As you present the overlay, set the presenting VC as the delegate. Then, call that delegate method from the presented VC. Then you can call any sort of actions from within the presenting VC before you call dismissViewController
In your overlay (ModalViewController.h):
@protocol ModalViewDelegate <NSObject>
-(void)didDismissModalView;
@end
@interface ModalViewController : UIViewController
@property(strong, nonatomic) id <ModalViewDelegate> delegate;
In your ModalViewController.m, call a method that calls your delegate method:
- (void)dismissModal{
[self.delegate didDismissModalView];
}
In your presenting VC h file: (PresentingViewController.h), make this class conform to your modal delegate protocol:
@interface PresentingViewController : UIViewController <ModalViewDelegate>
In your presenting VC, as you present the modal:
...
ModalViewController *modalViewController = [[ModalViewController alloc] init];
modalViewController.delegate = self; //the makes the presenting VC the delegate
[self presentViewController:modalViewController animated:YES completion:nil];
...
Finally, in your presenting VC, when you want to perform some actions before dismissing the modal, implement the ModalViewDelegate method:
- (void)didDismissModalView{
//DO SOME COOL STUFF, SET UP STUFF HERE, UPDATE UI, ETC
//Then dismiss the modal
[self dismissViewControllerAnimated:YES completion:^{
//Do more cool stuff
}];
}
All of this will work with your current custom transition code, but will give you more control over what happens before the modal/overlay is dismissed. Delegate is a beautiful thing.
Further, this will keep this animation code separate from code for other functionality, which is a bit cleaner IMO.
After much wrangling with this issue, I found the best solution which works in ios7 and ios8 is to leave modalPresentationStyle = UIModalPresentationFullScreen
instead of UIModalPresentationCustom
as the docs suggest.
If i do this as well as setting the transitioningDelegate
to my delegate, it still respects my transition and the will/diddisappear methods get called on the 'from' view controller. Also: no present-then-rotate-then-dismiss rotation issues to boot.
I have the same problem to call viewWillAppear
and other lifecycle methods.
What I did to solve it was implemented the delegate method func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController?
Then to make it work I do the following:
class ViewController: UIViewController {
...
func showViewController() {
// load your view controller as you want
guard let detailViewController = loadDetailViewcontroller() as? DetailViewController else {
return }
detailViewController.modalPresentationStyle = .custom
detailViewController.transitioningDelegate = self
present(detailViewController, animated: true, completion: nil)
}
}
extension ViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return PresentationController(presentedViewController: presented, presenting: presenting)
}
}
The PresentationController is like a temporary object for the presentation. Apple's documentation
From the time a view controller is presented until the time it is dismissed, UIKit uses a presentation controller to manage various aspects of the presentation process for that view controller. The presentation controller can add its own animations on top of those provided by animator objects, it can respond to size changes, and it can manage other aspects of how the view controller is presented onscreen.
Another solution could be using beginAppearanceTransition: and endAppearanceTransition:. According to documentation:
If you are implementing a custom container controller, use this method to tell the child that its views are about to appear or disappear. Do not invoke viewWillAppear:, viewWillDisappear:, viewDidAppear:, or viewDidDisappear: directly.
Here is how I used them:
- (void)animationEnded:(BOOL)transitionCompleted
{
if (!transitionCompleted)
{
_toViewController.view.transform = CGAffineTransformIdentity;
}
else
{
[_toViewController endAppearanceTransition];
}
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[toViewController beginAppearanceTransition:YES animated:YES];
// ... other code
}
But I still consider strange that custom modal presentation not doing this.