美文网首页
Objective-C & Swift的 自定义的转场动

Objective-C & Swift的 自定义的转场动

作者: 吊吊的plus | 来源:发表于2017-06-28 17:22 被阅读28次
    说到动画,就得说说transform属性了。

    1.所有的控件都有这个属性;
    2.transform能实现控件的平移、缩放、旋转的动画效果。

    下面是一个仿照push控制器效果的modal操作:
    管理者简介:

    .h文件:


    CKPresentationManager.h.png
    这个是转场动画的管理者类,这个头文件中包含了两个类:
    1.CKPresentationManager : 转场动画的管理者;
    2.CKPresentationController: 这个类是基于系统的 UIPresentationController 类。
    

    .m 文件:(下面3张图片都在这个.m文件中)


    图一.png 图二.png 图三.png
    这个.m文件中包含了两个类的实现 , 下面就来好好解读一下。
    

    正文:

    <一>首先谈谈 UIPresentationController 这个系统提供的类:

    1.UIPresentationController是提供高级视图切换的类。它让管理present ViewController的过程变得简单;
    2.通过重写该类,在子类中重写 - (void)containerViewDidLayoutSubviews 方法,可以修改弹出视图的frame,同时获取到两个非常重要的对象: containerView 和 - (nullable UIView *)presentedView ;
    3.containerView :是转场动画呈现视图的容器视图,所呈现的所有控件都是添加到这个容器视图上的;
    4.通过 - (nullable UIView *)presentedView方法可以拿到我们眼睛很直观所需要看到的视图,并可以对其进行操作更改。

    @interface CKPresentationController ()
    
    @property (nonatomic, strong) UIButton * coverBtn;
    
    @end
    
    @implementation CKPresentationController
    
    - (UIButton *)coverBtn {
        if (!_coverBtn) {
            _coverBtn = [UIButton buttonWithType:UIButtonTypeCustom];
            _coverBtn.frame = [UIScreen mainScreen].bounds;
            [_coverBtn addTarget:self action:@selector(coverBtnClickEvent) forControlEvents:UIControlEventTouchUpInside];
        }
        return _coverBtn;
    }
    
    /**
     *   重写系统方法
     *   用于布局转场动画弹出的控件,且只调一次
     */
    - (void)containerViewDidLayoutSubviews {
        //  containerView :非常重要,转场动画呈现视图的容器视图,转场动画呈现的所有控件,都是添加到这个容器视图上的
        //  - (nullable UIView *)presentedView :该方法非常重要,通过该方法可以拿到弹出的视图
        self.presentedView.frame = CGRectMake(0, 20, 200, 200);
        CGPoint center = self.presentedView.center;
        center.x = [UIScreen mainScreen].bounds.size.width * 0.5;
        self.presentedView.center = center;
        
        //  添加蒙版 - 便于点击空白处让视图消失
        [self.containerView insertSubview:self.coverBtn atIndex:0];
    }
    
    - (void)coverBtnClickEvent {
        //  让菜单消失
        [self.presentedViewController dismissViewControllerAnimated:true completion:nil];
    }
    
    @end
    
    <二>再来谈谈 CKPresentationManager这个类:

    1.CKPresentationManager :这个类集成NSObject,主要是作为转场动画的代理对象,将整个转场动画封装成一个独立的类,便于下次的使用,已经代码的可读性。
    2.CKPresentationManager 遵循了两个协议:
    <UIViewControllerTransitioningDelegate,UIViewControllerAnimatedTransitioning>
    3.UIViewControllerTransitioningDelegate协议:遵循该协议,主要是为了实现3个方法

    /**
     *    该方法返回一个赋值转场动画的对象
     *    CKPresentationController:可以在该对象中控制弹出视图的尺寸等
     */
    - (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(nullable UIViewController *)presenting sourceViewController:(UIViewController *)source NS_AVAILABLE_IOS(8_0) {
        return [[CKPresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting];
    }
    /**
     *    该方法用于返回一个负责转场动画如何 出现 的对象,需要实现相对应的代理方法
     */
    - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
        NSLog(@"呈现");
        _isPresent = YES;
        return self;
    }
    /**
     *    该方法用于返回一个负责转场动画如何 消失 的对象,需要实现相对应的代理方法
     */
    - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
        NSLog(@"消失");
        _isPresent = NO;
        return self;
    }
    

    4.UIViewControllerAnimatedTransitioning协议:遵循该协议,主要是为了实现2个方法

    /**
     *    该方法用于返回展示和消失动画的时长
     */
    - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
        return 0.5;
    }
    /**
     *    专门用于管理modal如何展示和消失,而且无论展现或者消失都会调用这个方法;
     *    注意:
     *       1.只有实现了这个代理方法,系统才不会使用默认的动画了;
     *       2.也就是说系统不会再添加从下至上的动画,所有的动画操作都需要我们自己去实现,包括需要展示的视图,也需要我们自己添加到 容器视图(containerView)上;
     *       3.transitionContext : 所有动画需要的东西都保存在这个上下文中,可以通过 transitionContext 获取我们想要的东西。
     */
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
        
        if (_isPresent) {
            //  展示
            //  获取需要弹出的视图
            UIView * toView = [transitionContext viewForKey:UITransitionContextToViewKey];
            //  将需要弹出的视图添加到容器视图上
            [transitionContext.containerView addSubview:toView];
            //  执行动画
            //toView.transform = CGAffineTransformMakeScale(1.0, 0.0);
            //  MakeTranslation : 基于最开始的位置形变,每一次形变都会把之前的形变清空,重新从最开始的位置形变
            toView.transform = CGAffineTransformMakeTranslation(-WIDTH, 0);
            //  动画是添加layer上的,所以涉及到需要设置锚点的概念,否则就是从展示的中间向两边展示,锚点默认为(0.5,0.5)
            toView.layer.anchorPoint = CGPointMake(0.5, 0.0);
            [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
                //  清空transform属性
                toView.transform = CGAffineTransformIdentity;
            } completion:^(BOOL finished) {
                //  注意:自定义的转场动画,在执行完毕后,一定要手动告诉系统
                [transitionContext completeTransition:true];
            }];
        } else {
            //  消失
            //  获取需要消失的视图
            UIView * fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
            //  执行动画让视图消失
            [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
                //
                //fromView.transform = CGAffineTransformMakeScale(1.0, 1.0);
                fromView.transform = CGAffineTransformMakeTranslation(WIDTH, 0);
            } completion:^(BOOL finished) {
                //  注意:自定义的转场动画,在执行完毕后,一定要手动告诉系统
                [transitionContext completeTransition:true];
            }];
        }
    }
    

    其中值得注意的地方是,动画是添加到layer上,这就需要引入一个概念 - 锚点 :
    锚点:( anchorPoint)
    1.anchorPoint点(锚点)的值是用相对bounds的比例值来确定的. 例如(0,0), (1,1)分别表示左上角、右下角,依此类推;
    2.锚点都是对于自身来讲的. 确定自身的锚点,通常用于做相对的tranform变换.当然也可以用来确定位置;
    3.锚点属性的类型为CGPoint,其默认值为(0.5, 0.5)。

    最后,就是这个类的使用了:
    ViewController.m.png

    在使用过程中,需要注意的是:
    1.在实例化CKPresentationManager管理者时,必须要强引用该类,否则就可能造成该管理者对象的提前释放;
    2.需要modal的控制器的modalPresentationStyle属性必须设置为UIModalPresentationCustom,否则就不是自定义的转场动画,且modal出控制器后,就会将原有的控制器移除。

    --- OC End ---

    =============================================================================
    --- Swift Start ---
    思路跟OC是一样的,直接上代码:
    CKPresentationManager管理者类:

    import UIKit
    
    class CKPresentationManager: NSObject, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning
    {
        //  定义标记记录当前是否是展现
        var isPresent = false
        
        //// MARK: - UIViewControllerTransitioningDelegate
        //  该方法用于返回一个负责转场动画的对象
        //  可以在该对象中控制弹出视图的尺寸等
        func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
            /// 自定义的UIPresentationController
            return CKPresentationController(presentedViewController: presented, presenting: presenting)
        }
        //  该方法用于返回一个负责转场如何出现的对象
        func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            NJLog(message: "展现")
            isPresent = true
            return self
        }
        //  该方法用于返回一个负责转场如何消失的对象
        func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            NJLog(message: "消失")
            isPresent = false
            return self
        }
    
        //// MARK: - UIViewControllerAnimatedTransitioning
        //  告诉系统展现和消失的动画时长 , 暂时用不上
        public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.5
        }
        
        //  专门用于管理modal如何展现和消失的,无论是展现还是消失都会调用该方法
        //  注意: 只要我们实现了这个代理方法,那么系统就不会再有默认的动画了;也就是说默认的modal从下自上的移动,系统也不会再帮我们添加了,所有的动画操作都需要我们自己去实现,包括需要展现的视图,也需要我们自己添加到容器视图上(containerView)
        //  transitionContext: 所有的动画需要的东西都保存在这个上下文中,换而言之就是可以通过transitionContext获取到我们想要的东西
        public func animateTransition(using transitionContext:UIViewControllerContextTransitioning) {
            
            //  判断当前是展现还是消失
            if isPresent {
                //  获取需要弹出的视图
                //        let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
                //        let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
                
                //  通过ToViewKey取出的就是toVC对应的view
                guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {
                    return
                }
                
                //  将需要弹出的视图添加到containerView上
                transitionContext.containerView.addSubview(toView)
                
                //  执行动画
                toView.transform = CGAffineTransform(scaleX: 1.0, y: 0.0)
                //  设置锚点,如果不设置就是从view的中心点,上下展开
                toView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0)
                UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
                    () -> Void in
                    //  清空transform属性
                    toView.transform = CGAffineTransform.identity
                    //  用下划线当作参数时,表示忽略这个值
                }) { (_) -> Void in
                    //  注意:自定义转场动画,在执行完动画之后,一定要告诉系统动画执行完毕了
                    transitionContext.completeTransition(true)
                }
            } else {
                //  通过FromViewKey取出的就是fromVC对应的view
                guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else {
                    return
                }
                //  执行动画让视图消失
                UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
                    () -> Void in
                    //  containerView突然消失,是因为CGFloat不准确,导致无法执行动画,遇到这样的问题只需要将CGFloat的值设置为一个很小的值即可
                    fromView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
                }, completion: { (_) -> Void in
                    transitionContext.completeTransition(true)
                })
            }
        }
    }
    
    class CKPresentationController: UIPresentationController {
        
        //  用于布局转场动画弹出的控件,只调一次
        override func containerViewDidLayoutSubviews() {
            //  containerView : 非常重要,转场动画呈现视图的容器view,所有的modal出来的视图都是添加到containerView上
            //  presentedView()  : 非常重要,通过该方法能够拿到弹出的视图
            presentedView?.frame = CGRect(x: 0, y: 55, width: 200, height: 200)
            presentedView?.center.x = UIScreen.main.bounds.width * 0.5
            
            //  添加蒙版view
            containerView?.insertSubview(coverbtn, at: 0)
            coverbtn.addTarget(self, action: #selector(coverBtnClick), for: UIControlEvents.touchUpInside)
        }
        
        //  MARK: - 懒加载
        private lazy var coverbtn: UIButton = {
            let btn = UIButton()
            btn.frame = UIScreen.main.bounds
            return btn
        }()
        
        //  btn点击事件
        @objc private func coverBtnClick() {
            //  让菜单消失
            presentedViewController.dismiss(animated: true, completion: nil)
        }
    }
    

    该管理者类在控制器中的使用:

    //  对转场动画manager的懒加载
        private lazy var animatorManager = CKPresentationManager()
    //  自定义转场动画
    //  自定义的转场动画时,modal出控制器后,不会把后面的控制器移除
    //  如果不自定义转场动画时,modal出来的控制器,会把原有的控制器移除
    //  如果不自定义转场动画时,modal出来的控制器的尺寸与屏幕一样
    //  如果自定义转场动画时,modal出来的控制器的尺寸,可以在自己的containerViewDidLayoutSubviews()方法中控制
    //  设置转场代理
        menuView.transitioningDelegate = animatorManager
     //  设置转场动画的样式
        menuView.modalPresentationStyle = UIModalPresentationStyle.custom
     //  弹出菜单
        present(menuView, animated: true, completion: nil)
    

    相关文章

      网友评论

          本文标题:Objective-C & Swift的 自定义的转场动

          本文链接:https://www.haomeiwen.com/subject/cgbucxtx.html