说到动画,就得说说transform属性了。
1.所有的控件都有这个属性;
2.transform能实现控件的平移、缩放、旋转的动画效果。
下面是一个仿照push控制器效果的modal操作:
管理者简介:
.h文件:
这个是转场动画的管理者类,这个头文件中包含了两个类:
1.CKPresentationManager : 转场动画的管理者;
2.CKPresentationController: 这个类是基于系统的 UIPresentationController 类。
.m 文件:(下面3张图片都在这个.m文件中)
这个.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)。
最后,就是这个类的使用了:
在使用过程中,需要注意的是:
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)