如何将视图控制器动画转换转换为 Segue 动画转换

Posted

技术标签:

【中文标题】如何将视图控制器动画转换转换为 Segue 动画转换【英文标题】:How To Convert A View Controller Animation Transition To A Segue Animation Transition 【发布时间】:2013-11-03 16:34:38 【问题描述】:

据我所知,无法将 View Controller 动画用作 Segue 过渡。目前,我在现有项目中有一组视图控制器动画,用于在视图控制器之间创建动画转换。例如:

模态动画.h文件:

//
//  MVModalDismissAnimation.h
//

#import "MVBaseAnimation.h"

@interface MVModalAnimation : MVBaseAnimation

@end

模态动画 .m 文件:

//
//  MVModalDismissAnimation.m
//

#import "MVModalAnimation.h"

@implementation MVModalAnimation 
UIView *_coverView;
NSArray *_constraints;


#pragma mark - Animated Transitioning

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext 
//The view controller's view that is presenting the modal view
UIView *containerView = [transitionContext containerView];

if (self.type == AnimationTypePresent) 
    //The modal view itself
    UIView *modalView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;

    //View to darken the area behind the modal view
    if (!_coverView) 
        _coverView = [[UIView alloc] initWithFrame:containerView.frame];
        _coverView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.6];
        _coverView.alpha = 0.0;
     else _coverView.frame = containerView.frame;
    [containerView addSubview:_coverView];

    //Using autolayout to position the modal view
    modalView.translatesAutoresizingMaskIntoConstraints = NO;
    [containerView addSubview:modalView];
    NSDictionary *views = NSDictionaryOfVariableBindings(containerView, modalView);
    _constraints = [[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-30-[modalView]-30-|" options:0 metrics:nil views:views] arrayByAddingObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-30-[modalView]-30-|" options:0 metrics:nil views:views]];
    [containerView addConstraints:_constraints];

    //Move off of the screen so we can slide it up
    CGRect endFrame = modalView.frame;
    modalView.frame = CGRectMake(endFrame.origin.x, containerView.frame.size.height, endFrame.size.width, endFrame.size.height);
    [containerView bringSubviewToFront:modalView];

    //Animate using spring animation
    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.8 initialSpringVelocity:1.0 options:0 animations:^
        modalView.frame = endFrame;
        _coverView.alpha = 1.0;
     completion:^(BOOL finished) 
        [transitionContext completeTransition:YES];
    ];
 else if (self.type == AnimationTypeDismiss) 
    //The modal view itself
    UIView *modalView = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view;

    //Grab a snapshot of the modal view for animating
    UIView *snapshot = [modalView snapshotViewAfterScreenUpdates:NO];
    snapshot.frame = modalView.frame;
    [containerView addSubview:snapshot];
    [containerView bringSubviewToFront:snapshot];
    [modalView removeFromSuperview];

    //Set the snapshot's anchor point for CG transform
    CGRect originalFrame = snapshot.frame;
    snapshot.layer.anchorPoint = CGPointMake(0.0, 1.0);
    snapshot.frame = originalFrame;

    //Animate using keyframe animation
    [UIView animateKeyframesWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:0 animations:^
        [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.15 animations:^
            //90 degrees (clockwise)
            snapshot.transform = CGAffineTransformMakeRotation(M_PI * -1.5);
        ];
        [UIView addKeyframeWithRelativeStartTime:0.15 relativeDuration:0.10 animations:^
            //180 degrees
            snapshot.transform = CGAffineTransformMakeRotation(M_PI * 1.0);
        ];
        [UIView addKeyframeWithRelativeStartTime:0.25 relativeDuration:0.20 animations:^
            //Swing past, ~225 degrees
            snapshot.transform = CGAffineTransformMakeRotation(M_PI * 1.3);
        ];
        [UIView addKeyframeWithRelativeStartTime:0.45 relativeDuration:0.20 animations:^
            //Swing back, ~140 degrees
            snapshot.transform = CGAffineTransformMakeRotation(M_PI * 0.8);
        ];
        [UIView addKeyframeWithRelativeStartTime:0.65 relativeDuration:0.35 animations:^
            //Spin and fall off the corner
            //Fade out the cover view since it is the last step
            CGAffineTransform shift = CGAffineTransformMakeTranslation(180.0, 0.0);
            CGAffineTransform rotate = CGAffineTransformMakeRotation(M_PI * 0.3);
            snapshot.transform = CGAffineTransformConcat(shift, rotate);
            _coverView.alpha = 0.0;
        ];
     completion:^(BOOL finished) 
        [_coverView removeFromSuperview];
        [containerView removeConstraints:_constraints];
        [transitionContext completeTransition:YES];
    ];



-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext 
if (self.type == AnimationTypePresent) return 1.0;
else if (self.type == AnimationTypeDismiss) return 1.75;
else return [super transitionDuration:transitionContext];


@end

上面是从底部向上滑动一个模态视图控制器。

我现在想将其转换为与故事板和转场一起使用。

我想要转换其他四种类型的动画,那么有没有一种简单的方法可以使上述类与 segue 转换一起使用?

【问题讨论】:

【参考方案1】:

我可能会误解你,但这里有一个与自定义 segue 一起使用的 segue 示例...

@interface TestSegue : UIStoryboardSegue

@end

@implementation TestSegue
-(void)perform
    
    UIView *sv = ((UIViewController *)self.sourceViewController).view;
    UIView *dv = ((UIViewController *)self.destinationViewController).view;

    UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
    [window insertSubview:dv aboveSubview:sv];
    [dv enterRight:0.1 then:^
        
        [self.sourceViewController
           presentViewController:self.destinationViewController
           animated:NO completion:nil];
        ];
    
    
@end

注意我的例程“enterRight:”只是 UIView 上的一个类别

使用自定义 segues 的便捷教程 ...http://www.bencz.com/hacks/2014/02/07/custom-ios-segues-in-xcode-5/

我在 UIView 上的类别的一个例子,它只是一个视图的移动动画...

请注意,您需要并且必须有一个“after”块,才能在自定义 segue 中使用它。

注意 - 对于不熟悉 https://***.com/a/20760583/294884 的任何人阅读,这里有一个关于将块作为属性的完整说明

-(void)enterRight:(float)delay then:(void(^)(void))after
    
    CGPoint moveTo = self.center;
    CGPoint moveFrom = self.center;
    
    CGFloat simpleOffscreen = [UIScreen mainScreen].bounds.size.width;
    
    moveFrom.x = moveFrom.x + simpleOffscreen;
    
    self.center = moveFrom;
    self.hidden = NO;
    
    [UIView animateWithDuration:0.5
        delay:delay
        usingSpringWithDamping:0.4
        initialSpringVelocity:0.1
        options:0
        animations:^
            
            self.center = moveTo;
            
        completion:^(BOOL finished)
            
            if (after) after();
            
        ];
    

所以,如果您从类别开始...

在 Xcode 中单击添加新文件,然后选择类别。有两个盒子。

所以,一个类别“在”某个类。在这种情况下,它是“打开”的 UIView。在底部框中,输入 UIView。

在顶部框中,简单地输入一些方便、简短的名称。事实上,使用的名称并不重要。例如,典型的选择是“实用程序”“助手”“技巧”“动漫”或类似的选项。

现在转到 .h 文件并添加此文件

-(void)enterRight:(float)delay then:(void(^)(void))after;

现在转到 .m 文件并准确添加上面的函数。您现在可以在代码中的任何位置使用该函数。这是一个使用它的例子

-(void)viewDidLoad
    
    [super viewDidLoad];
    [PFAnalytics trackEvent:@"harvestFBPage"];
    
    [self.slideOff showMessage:@"Next step..."];
    [self.slideOff enterRight:0.0 then:nil];
    

因此,对于任何 UIView,您都可以使用 [anyUIView enterRight...];,这很棒。在示例中 self.slideOff 是一个 UIView。

请注意,在该示例中,“之后无事可做”。这是一个“事后要做的事情”的例子。

-(void)viewDidLoad
    
    [super viewDidLoad];
    [PFAnalytics trackEvent:@"harvestFBPage"];
    
    [self.slideOff showMessage:@"Next step..."];
    [self.slideOff enterRight:0.0 then:^
        
        NSLog(@"do this afterwards!");
        self.slideOff.alpha = 0.5;
        etc;
        etc;
        ];
    

希望对分类有所帮助。如果您对块作为参数不太熟悉,请不要忘记去这里https://***.com/a/20760583/294884。


回到手头的问题。

不可能在自定义转场中使用任何类型的动画,除非它包含“完成后运行此块”的概念,因为您必须向 presentViewController 发送消息:(带动画:NO)仅在您完成后

假设我理解了你的问题,那就是关键。

【讨论】:

这很激烈!我仍在尝试制定将块声明为UIView 类别上的属性的确切语法。我想我了解您如何使用这些块以及它们是如何被传递的,但我不太确定如何为此类类别实现标头。你能给我一点方向吗? 谢谢!! 哈哈,我只是少了一个分号,哈哈。我想有时你只需要从盯着屏幕上休息一下。另一个快速的问题:在我的视图层次结构中,我有一个UITransitionView 在每次执行后堆积在我的呈现视图后面。在此解决方案中是否存在阻止过渡解决的问题? 这是一个棘手的问题,可能会出现各种问题。我建议一个新问题,包括您的所有代码,请随时在此处留下指向新问题的链接【参考方案2】:

UIStoryBoardSegue 是一个包含过渡动画并神奇地出现在情节提要中的对象。 可以在自定义转场中使用自定义视图控制器过渡动画

假设您已经有一个实现UIViewControllerAnimatedTransitioning 协议的自定义动画对象(如上),您需要:

    创建一个自定义 segue 来包装动画师,并通过子类化 UIStoryBoardSegue 使其在您的故事板中可用

    @interface MYCustomAnimationSegue : UIStoryboardSegue<UIViewControllerTransitioningDelegate>
    @end
    

    实现perform: 方法,将其设置为transitioningdelegate(用于模态转换)或navigationController 的委托(用于推送或替换转换)

    @implementation MYCustomAnimationSegue
    
    - (void)perform 
        UIViewController *sourceVC = (UIViewController *)self.sourceViewController;
        UIViewController *destVC = (UIViewController *)self.destinationViewController;
    
        //Set the transitioningdelegate (for a modal transition)
        destVC.transitioningDelegate = self;
    
        //strong property, necessary to retain the Segue after perform: is finished
        //only needed if you want the customSegue to be the transitioningDelegate
        sourceVC.customSegue = self;
    
        //Set the modalPresentationStyle
        if (&UITransitionContextFromViewKey) 
            //iOS8
            destVC.modalPresentationStyle = UIModalPresentationFullScreen;
         else 
            //iOS7
            destVC.modalPresentationStyle = UIModalPresentationCustom;
        
    
        //Call the presentation
        [sourceVC presentViewController:destVC animated:YES completion:^
            //release ourselves again
            sourceVC.customSegue = nil;
        ];
    
    
    @end
    

    实现UIViewControllerTransitioningDelegate 方法以返回自定义动画对象

    - (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source 
    
        return [[MyCustomAnimator alloc] init];
    
    

    当您从一个视图控制器按住 ctrl 并拖动到另一个视图控制器时,您的自定义 segue 类现在将显示在 Interface Builder 中。

在您兴奋之前,有一个警告:这只适用于一个方向。要使其适用于 Unwind Segue,您的视图控制器类需要实现 necessary methods。

【讨论】:

以上是关于如何将视图控制器动画转换转换为 Segue 动画转换的主要内容,如果未能解决你的问题,请参考以下文章

Xcode 界面生成器 Segue 动画

如何将两个视图控制器显示为模态但仅对第二个进行动画转换?

动画状态栏以及segue中的自定义转换

模态 Segue 进入没有导航栏的导航控制器

视图控制器转换动画子视图位置

在没有 UINavigationController 的情况下推送 Segue 动画