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

说到动画,就得说说transform属性了。

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

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

.h文件:


Objective-C & Swift的 自定义的转场动画_第1张图片
CKPresentationManager.h.png
这个是转场动画的管理者类,这个头文件中包含了两个类:
1.CKPresentationManager : 转场动画的管理者;
2.CKPresentationController: 这个类是基于系统的 UIPresentationController 类。

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


图一.png
图二.png
Objective-C & Swift的 自定义的转场动画_第2张图片
图三.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 遵循了两个协议:

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 )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    NSLog(@"呈现");
    _isPresent = YES;
    return self;
}
/**
 *    该方法用于返回一个负责转场动画如何 消失 的对象,需要实现相对应的代理方法
 */
- (nullable id )animationControllerForDismissedController:(UIViewController *)dismissed {
    NSLog(@"消失");
    _isPresent = NO;
    return self;
}

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

/**
 *    该方法用于返回展示和消失动画的时长
 */
- (NSTimeInterval)transitionDuration:(nullable id )transitionContext {
    return 0.5;
}
/**
 *    专门用于管理modal如何展示和消失,而且无论展现或者消失都会调用这个方法;
 *    注意:
 *       1.只有实现了这个代理方法,系统才不会使用默认的动画了;
 *       2.也就是说系统不会再添加从下至上的动画,所有的动画操作都需要我们自己去实现,包括需要展示的视图,也需要我们自己添加到 容器视图(containerView)上;
 *       3.transitionContext : 所有动画需要的东西都保存在这个上下文中,可以通过 transitionContext 获取我们想要的东西。
 */
- (void)animateTransition:(id )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)。

最后,就是这个类的使用了:
Objective-C & Swift的 自定义的转场动画_第3张图片
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的 自定义的转场动画)