iOS - 小总结 Demo -- 实现自定义转场 Push、Pop 、Present、Dissmiss 动画

3,461 阅读4分钟
原文链接: www.jianshu.com

小结一下跳转页面的动画效果实现思路!

总结的转场动画是下面几个情况:

  • 导航控制器的 Push 动画和 Pop 动画
  • 普通控制器的 Present 动画和 Dismiss动画,

思路简析:

  • 跳转的控制器遵守 UINavigationControllerDelegate 协议,从而实现 Pop 和 Push 的跳转动画。
# 通过 operation == UINavigationControllerOperationPush 或者 UINavigationControllerOperationPop 区分是 Push 还是 Pop
- (nullable id )navigationController:(UINavigationController *)navigationController
 animationControllerForOperation:(UINavigationControllerOperation)operation
 fromViewController:(UIViewController *)fromVC
 toViewController:(UIViewController *)toVC
  • 跳转的控制器遵守UIViewControllerTransitioningDelegate协议, 从而实现 Present 和 Dissmiss跳转动画
# Present:
 - (nullable id )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
# Dissmiss:
 - (nullable id )animationControllerForDismissedController:(UIViewController *)dismissed;
  • 上述的几个方法都是 返回一个遵守UIViewControllerAnimatedTransitioning协议的对象,而主要的动画实现就是写在这个对象当中!我们只要封装好这个类型的对象,在上述方法中返回对应实例对象就能实现动画效果!
# UIViewControllerAnimatedTransitioning协议 主要实现两个方法
-(NSTimeInterval)transitionDuration: transitionContext: 跳转的时间
-(void)animateTransition: transitionContext 所要执行的动画在这里实现

其中第二个方法传入的参数是泛型的UIViewControllerContextTransitioning对象
可以通过下面它的的实例方法获取我们需要展示动画的相关属性

#  这个方法获得的是 控制整个跳转的页面 (API 描述:这个视图是动画发生的地方(画布))(将要跳转到的控制器的view添加到画布上执行动画)
- (nullable UIView *)containerView
#  Key 取值 UITransitionContextFromViewControllerKey  (源控制器)UITransitionContextToViewControllerKey(目标控制器)
- (nullable __kindof UIViewController *)viewControllerForKey:(NSString *)key
#  Key 取值  UITransitionContextFromViewKey(源视图)  UITransitionContextToViewKey(目标视图)
- (nullable __kindof UIView *)viewForKey:(NSString *)key

简单效果展示:


演示——跳转.gif

上代码:

第一步: 是封装一个实现动画的类(PP_Transition),遵循UIViewControllerAnimatedTransitioning协议

PP_Transition.h中

#import 
# 枚举判断使用场景
typedef NS_OPTIONS(NSUInteger, AnimatedScene)
{
    AnimatedScenePush = 0,         // 值为 0
    AnimatedScenePop = 1 << 0,     // 值为 2 的 0次
    AnimatedScenePresent = 1 << 1, // 值为 2 的 1次
    AnimatedSceneDissmiss = 1 << 2 // 值为 2 的 2次
};
- (instancetype)initWithStytle:(AnimatedScene)scene;

PP_Transition.m中

{
    AnimatedScene _scenceStyle;
}
- (instancetype)initWithStytle:(AnimatedScene)scene
{
    if (self = [super init])
    {
        _scenceStyle = scene;
    }
    return self;
}
- (NSTimeInterval)transitionDuration:(id)transitionContext{
    return 1.0;
}
  • 实现协议方法2: 具体去实现转场的动画 (这里我就是简单的实现几个动画, 我们可以根据具体的情况加以实现)
    • 如果我们想加一些更好的效果,可以尝试在画布(containerView) View 上加上一些自定义的View 用来遮挡或者当做背景(比如 Pop回来时候目标View变大时候加一个背景),记住最后移除掉就行!这是一种思路!
- (void)animateTransition:(id)transitionContext
{
    // 获取到 containerView视图 (我们动画发生的载体)
    UIView *containerView = [transitionContext containerView];
    // 我们要去的 View
    UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    // 从哪个 View 去的
    UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
     switch (_scenceStyle)
    {
        case AnimatedScenePush:
        {// Push 动画 这里只是举个例子  动画效果可以自己去使用
            // 注意点: 一定要把目的视图(要去的 View) 添加到容器(containerView)上.
            [UIView animateWithDuration:1.0 animations:^{
                fromView.transform = CGAffineTransformMakeScale(0.5, 0.5);
            } completion:^(BOOL finished) {
                [containerView addSubview:toView];
                // 这个方法大概就是完成过渡动画,更新内部视图,控制器状态的转变!
                [transitionContext completeTransition:YES];
            }];

            NSLog(@"Push 动画效果");
        }
           break;
        case AnimatedScenePop:
        {
            // Pop 动画
            [UIView animateWithDuration:1.0 animations:^{
                // 让当前的二级页面  从下方消失
                fromView.frame = CGRectMake(0, kScreenH, kScreenW, kScreenH);

            } completion:^(BOOL finished) {

                [containerView addSubview:toView];

                [UIView animateWithDuration:1.0 animations:^{
                    // 让首级页面  由小变大
                    toView.transform = CGAffineTransformMakeScale(1, 1);

                } completion:^(BOOL finished) {

                    // 完成过度动画
                    [transitionContext completeTransition:YES];
                }];
            }];
            NSLog(@"Pop 动画效果");

        }

            break;
        case AnimatedScenePresent:
        {

            toView.frame = CGRectMake(kScreenW / 2.0 , kScreenH / 2.0, 0, 0);

            [containerView addSubview:toView];

            [UIView animateWithDuration:1.0 animations:^{

               toView.frame = CGRectMake(0 , 0, kScreenW, kScreenH);

            } completion:^(BOOL finished) {

                [transitionContext completeTransition:YES];
            }];

            NSLog(@"Present 动画效果");


        }
            break;
        case AnimatedSceneDissmiss:
        {

            [UIView animateWithDuration:1.0 animations:^{
            // 让当前的二级页面  从上方消失
                fromView.frame = CGRectMake(0, -kScreenH, kScreenW, kScreenH);

            } completion:^(BOOL finished) {

                [containerView addSubview:toView];
            // 完成过度动画
                [transitionContext completeTransition:YES];

            }];
            NSLog(@"Dissmiss 动画效果");

        }
            break;

        default:
            break;
    }

}

在跳转的控制器中:

  • 首先遵守代理
  • 设置代理

       // 设置 导航控制器代理完成 push 和 pop
      self.navigationController.delegate = self;
       // 设置 模态转场过渡代理
      目的控制器Vc.transitioningDelegate = self;
      目的控制器Vc.modalPresentationStyle = UIModalPresentationCustom;
  • 代理方法实现

// 导航控制器的跳转动画代理方法  在这里完成 Push 和 Pop 动画
- (id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
    return operation == UINavigationControllerOperationPush ? [[PP_Transition alloc] initWithStytle:(AnimatedScenePush)] : [[PP_Transition alloc] initWithStytle:(AnimatedScenePop)];
}

// 完成转场 Present 动画代理
- (id)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
    NSLog(@"----------->Present 一级视图控制器中");
    return [[PP_Transition alloc] initWithStytle:(AnimatedScenePresent)];
}
// 转场 Dissmiss 动画 代理
- (id)animationControllerForDismissedController:(UIViewController *)dismissed
{
    NSLog(@"----------->Dissmiss 一级视图控制器中");
    return [[PP_Transition alloc] initWithStytle:(AnimatedSceneDissmiss)];
}

最后说一句:

我开始写的类名不太好, 弄了一半想改的话可以这样!

.h选中要改的类名 ---> 菜单栏选中 Edit ---> Refactor ---> Rename


改过之后,之前用到这个类名的地方全部都换过来了.png