自定义转换动画不调用VC生命周期方法

时间:2022-09-07 08:22:04

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.

所以我建立了一个自定义的表示转换动画,一切似乎都很好,除了视图控制器生命周期方法没有被调用。

Before presenting the controller I use the UIModalPresentationCustom style to keep the presenting VC in the view hierarchy, but once I dismiss the presented VC, viewWillAppear and viewDidAppear are not called on my presenting controller. Am I missing a step that I need to explicitly call to get those methods to fire? I know manually calling those methods is not the correct solution.

在呈现控制器之前,我使用UIModalPresentationCustom样式将呈现的VC保存在视图层次结构中,但是一旦我取消了呈现的VC, viewWillAppear和viewDidAppear就不会调用我的呈现控制器。我是否遗漏了一个需要显式调用的步骤,以使这些方法触发?我知道手动调用这些方法不是正确的解决方案。

Here is my dismissing animation code. I'm basically animating a form overlay view to shrink to the size of a collection view cell on dismissal.

这是我的解散动画代码。我基本上是在创建一个表单叠加视图,缩小到被解雇时的集合视图单元的大小。

- (void)_animateDismissingTransitionWithContext:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

    UICollectionView *destinationCollectionView = toCollectionViewController.collectionView;
    UICollectionViewCell *destinationCollectionViewCell = [self _destinationCellFromContext:transitionContext];
    UIView *containerView = transitionContext.containerView;

    // Calculate frames    
    CGRect startFrame = fromEventDetailViewController.detailContainerView.frame;
    CGRect endFrame = [destinationCollectionView convertRect:destinationCollectionViewCell.frame toView:containerView];

    // Add overlay
    UIView *overlayView = [UIView new];
    overlayView.backgroundColor = [UIColor overlayBackground];
    overlayView.frame = containerView.bounds;
    overlayView.alpha = 1.0f;
    [containerView addSubview:overlayView];

    // Add fake detail container view
    UIView *fakeContainerView = [UIView new];
    fakeContainerView.backgroundColor = fromEventDetailViewController.detailContainerView.backgroundColor;
    fakeContainerView.frame = startFrame;
    [containerView addSubview:fakeContainerView];

    // Hide from view controller
    fromEventDetailViewController.view.alpha = 0.0f;

    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0f usingSpringWithDamping:0.75f initialSpringVelocity:0.2f options:UIViewAnimationOptionCurveEaseOut animations:^{
        fakeContainerView.frame = endFrame;
        fakeContainerView.backgroundColor = [UIColor eventCellBackground];

        overlayView.alpha = 0.0f;
    } completion:^(BOOL finished) {
        [fromEventDetailViewController.view removeFromSuperview];
        [overlayView removeFromSuperview];
        [fakeContainerView removeFromSuperview];

        [transitionContext completeTransition:YES];
    }];
}

6 个解决方案

#1


28  

Another solution could be using beginAppearanceTransition: and endAppearanceTransition:. According to documentation:

另一种解决方案是使用beginAppearanceTransition:和endAppearanceTransition:。根据文档:

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.

如果您正在实现一个自定义容器控制器,请使用这个方法来告诉孩子它的视图即将出现或消失。不要调用viewWillAppear:, viewWillDisappear:, viewDidAppear:,或viewDidDisappear:直接。

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.

但我仍然觉得奇怪的是,定制的模态演示并没有这样做。

#2


8  

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.

在与这个问题争论了很久之后,我发现在ios7和ios8中最有效的解决方案是保留modalPresentationStyle = UIModalPresentationFullScreen,而不是像文档所建议的那样保留UIModalPresentationCustom。

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.

如果我将transitioningDelegate和委托设置得一样好,它仍然尊重我的转换,will/diddisappear方法会在“from”视图控制器上被调用。同样:不存在当前-旋转-取消旋转问题。

#3


6  

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.

如果你在使用UIModalPresentationCustom你应该提供自定义的UIPresentationController类,如果你想使用ViewController生命周期调用者,你需要覆盖shouldRemovePresentersView并返回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.

如果您希望保持呈现者并且仍然有ViewControlelr生命周期回调,您可以覆盖private method _shoulddisablepressappearance .外观,并在自定义UIPresentationController类中返回NO。

#4


3  

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.

这是一个模态演示。我不认为viewWillAppear和viewDidAppear是通过使用该方法的自定义转换调用的,因为从技术上讲,视图在视图层次结构中仍然是活动的。我建议您在这里使用委托,就像您通常使用的模式一样。

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

在给定的VC上创建委托协议。创建一个委托方法,可以从呈现的VC中调用。当你呈现叠加时,将呈现的VC设置为委托。然后,调用前面的VC中的委托方法。然后你可以在调用dismissViewController之前,从present VC中调用任何类型的操作

In your overlay (ModalViewController.h):

在你的覆盖(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:

在你ModalViewController。m,调用一个调用委托方法的方法:

- (void)dismissModal{
    [self.delegate didDismissModalView];
}

In your presenting VC h file: (PresentingViewController.h), make this class conform to your modal delegate protocol:

在您呈现的VC h文件中:(PresentingViewController.h),使这个类符合您的模态委托协议:

@interface PresentingViewController : UIViewController <ModalViewDelegate>

In your presenting VC, as you present the modal:

在你的呈现VC中,当你呈现模态时:

...
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:

最后,在你的呈现VC中,当你想在取消模态之前执行一些操作时,实现ModalViewDelegate方法:

- (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.

此外,这将使这个动画代码与其他功能的代码分离,这在我看来更加简洁。

#5


1  

@John Tracids' anser solved my issue. Thanks John!

@John Tracids' anser解决了我的问题。谢谢你约翰!

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.

如果你用modalPresentationStyle = .custom (objc UIModalPresentationCustom)显示UIViewController实例,以保持对viewcontroller的生命周期方法的调用,你必须显式地管理viewcontroller的外观。要做到这一点,只需调用动画完成块之前的begin外观转换和结束外观转换。

Also you can pass to your transitioning animator class custom UIPresentationController subclass with overridden value shouldRemovePresentersView returning true without calling beginAppearanceTransition

您还可以传递到您的转换动画器类自定义的UIPresentationController子类,该子类具有重写的值shouldRemovePresentersView返回true而不调用beginAppearanceTransition

// Swift 4

/ /斯威夫特4

put this to your custom UIViewControllerAnimatedTransitioning class before animation

在动画之前,将它放到您的自定义uiviewcontrolleranimatedtransi类中

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 }
}

#6


1  

It is wrong to call begin/end appearance methods at animation process. What happened if you present view controller without animation? Right, presenting view controller will not receive appearance callbacks. You must implement custom UIPresentationController and call begin/end appearance methods. Here sample:

在动画过程中调用开始/结束外观方法是错误的。如果你现在没有动画的视图控制器会发生什么?好的,present视图控制器不会接收外观回调。您必须实现自定义UIPresentationController并调用start /end外观方法。以下示例:

@interface CustomPresentationController : UIPresentationController

@end

@implementation CustomPresentationController

- (void)presentationTransitionWillBegin
{
    [self.presentingViewController beginAppearanceTransition:NO animated:NO];
}

- (void)presentationTransitionDidEnd:(BOOL)completed
{
   [self.presentingViewController endAppearanceTransition];
}

- (void)dismissalTransitionWillBegin
{
   [self.presentingViewController beginAppearanceTransition:YES animated:NO];
}

- (void)dismissalTransitionDidEnd:(BOOL)completed
{
   [self.presentingViewController endAppearanceTransition];
}

@end

#1


28  

Another solution could be using beginAppearanceTransition: and endAppearanceTransition:. According to documentation:

另一种解决方案是使用beginAppearanceTransition:和endAppearanceTransition:。根据文档:

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.

如果您正在实现一个自定义容器控制器,请使用这个方法来告诉孩子它的视图即将出现或消失。不要调用viewWillAppear:, viewWillDisappear:, viewDidAppear:,或viewDidDisappear:直接。

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.

但我仍然觉得奇怪的是,定制的模态演示并没有这样做。

#2


8  

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.

在与这个问题争论了很久之后,我发现在ios7和ios8中最有效的解决方案是保留modalPresentationStyle = UIModalPresentationFullScreen,而不是像文档所建议的那样保留UIModalPresentationCustom。

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.

如果我将transitioningDelegate和委托设置得一样好,它仍然尊重我的转换,will/diddisappear方法会在“from”视图控制器上被调用。同样:不存在当前-旋转-取消旋转问题。

#3


6  

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.

如果你在使用UIModalPresentationCustom你应该提供自定义的UIPresentationController类,如果你想使用ViewController生命周期调用者,你需要覆盖shouldRemovePresentersView并返回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.

如果您希望保持呈现者并且仍然有ViewControlelr生命周期回调,您可以覆盖private method _shoulddisablepressappearance .外观,并在自定义UIPresentationController类中返回NO。

#4


3  

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.

这是一个模态演示。我不认为viewWillAppear和viewDidAppear是通过使用该方法的自定义转换调用的,因为从技术上讲,视图在视图层次结构中仍然是活动的。我建议您在这里使用委托,就像您通常使用的模式一样。

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

在给定的VC上创建委托协议。创建一个委托方法,可以从呈现的VC中调用。当你呈现叠加时,将呈现的VC设置为委托。然后,调用前面的VC中的委托方法。然后你可以在调用dismissViewController之前,从present VC中调用任何类型的操作

In your overlay (ModalViewController.h):

在你的覆盖(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:

在你ModalViewController。m,调用一个调用委托方法的方法:

- (void)dismissModal{
    [self.delegate didDismissModalView];
}

In your presenting VC h file: (PresentingViewController.h), make this class conform to your modal delegate protocol:

在您呈现的VC h文件中:(PresentingViewController.h),使这个类符合您的模态委托协议:

@interface PresentingViewController : UIViewController <ModalViewDelegate>

In your presenting VC, as you present the modal:

在你的呈现VC中,当你呈现模态时:

...
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:

最后,在你的呈现VC中,当你想在取消模态之前执行一些操作时,实现ModalViewDelegate方法:

- (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.

此外,这将使这个动画代码与其他功能的代码分离,这在我看来更加简洁。

#5


1  

@John Tracids' anser solved my issue. Thanks John!

@John Tracids' anser解决了我的问题。谢谢你约翰!

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.

如果你用modalPresentationStyle = .custom (objc UIModalPresentationCustom)显示UIViewController实例,以保持对viewcontroller的生命周期方法的调用,你必须显式地管理viewcontroller的外观。要做到这一点,只需调用动画完成块之前的begin外观转换和结束外观转换。

Also you can pass to your transitioning animator class custom UIPresentationController subclass with overridden value shouldRemovePresentersView returning true without calling beginAppearanceTransition

您还可以传递到您的转换动画器类自定义的UIPresentationController子类,该子类具有重写的值shouldRemovePresentersView返回true而不调用beginAppearanceTransition

// Swift 4

/ /斯威夫特4

put this to your custom UIViewControllerAnimatedTransitioning class before animation

在动画之前,将它放到您的自定义uiviewcontrolleranimatedtransi类中

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 }
}

#6


1  

It is wrong to call begin/end appearance methods at animation process. What happened if you present view controller without animation? Right, presenting view controller will not receive appearance callbacks. You must implement custom UIPresentationController and call begin/end appearance methods. Here sample:

在动画过程中调用开始/结束外观方法是错误的。如果你现在没有动画的视图控制器会发生什么?好的,present视图控制器不会接收外观回调。您必须实现自定义UIPresentationController并调用start /end外观方法。以下示例:

@interface CustomPresentationController : UIPresentationController

@end

@implementation CustomPresentationController

- (void)presentationTransitionWillBegin
{
    [self.presentingViewController beginAppearanceTransition:NO animated:NO];
}

- (void)presentationTransitionDidEnd:(BOOL)completed
{
   [self.presentingViewController endAppearanceTransition];
}

- (void)dismissalTransitionWillBegin
{
   [self.presentingViewController beginAppearanceTransition:YES animated:NO];
}

- (void)dismissalTransitionDidEnd:(BOOL)completed
{
   [self.presentingViewController endAppearanceTransition];
}

@end