自定义Modal转场-模仿push & pop

前言:

  • 昨天发了一篇文章 多层present,dismiss回到首次present的控制器 中提到自定义转场动画,实现从一个透明的导航控制器 跳转到 非透明的导航控制器,其中第一种方法就是通过自定义modal 转场,模仿push & pop 实现,本文章中将会详细说明

  • 先上效果图: gitHub地址 喜欢就给个star 呗~

自定义Modal转场-模仿push & pop_第1张图片
1.gif

自定义转场需要用到什么东西?

  • 1.UIViewControllerAnimatedTransitioning 一个协议,这个接口负责切换的具体内容,也即“切换中应该发生什么”,动画实现只需要实现其两个代理方法就行

  • 2.UIViewControllerTransitioningDelegate 一个协议,需要VC切换的时候系统会向实现了这个接口的对象询问是否需要使用自定义的切换效果,也就是选择自定义的动画

  • 3.UIPresentationController 控制控制器跳转的类,是 iOS8 新增的一个 API,用来控制 controller 之间的跳转特效,例如:显示一个模态窗口,大小和位置是自定义的,遮罩在原来的页面。参考

  • 4.modalPresentationStyle 这是UIViewController 的一个属性,就是字面意思,modal的样式,自定义的话,需要设置为Custom

  • 5.transitioningDelegate 就是谁去实现 UIViewControllerTransitioningDelegate 这个协议的代理方法,一般用一个专门的类管理,接着看下去就知道啦~

一步一个脚印,代码实现来了,具体分析代码中有注释~

  • 1.首先新建一个继承NSObject 的动画管理类FLTransitionAnimation,管理是执行present 、dismiss 动画 ,在m文件的延展中遵守UIViewControllerAnimatedTransitioning 协议并实现两个代理方法
.h文件:
* * * * * * * * * * * * * * * * * * * * * * * *
#import 

// present or dismiss
typedef enum{
    FLTransitionAnimationTypePresent,
    FLTransitionAnimationTypeDismiss
}FLTransitionAnimationType;

// present direction
typedef enum{
    FLPresentDirectionTypeFromLeft,
    FLPresentDirectionTypeFromRight
}FLPresentDirectionType;

// present direction
typedef enum{
    FLDismissDirectionTypeFromLeft,
    FLDismissDirectionTypeFromRight
}FLDismissDirectionType;

@interface FLTransitionAnimation : NSObject

/**
 *  @author 孔凡列, 16-09-02 02:09:13
 *
 *  present or dismiss
 */
@property (nonatomic,assign)FLTransitionAnimationType fl_transitionAnimationType;
/**
 *  @author 孔凡列, 16-09-02 04:09:56
 *
 *  present direction
 */
@property (nonatomic,assign)FLPresentDirectionType fl_presentDirectionType;
/**
 *  @author 孔凡列, 16-09-02 04:09:05
 *
 *  dismiss direction
 */
@property (nonatomic,assign)FLDismissDirectionType fl_dismissDirectionType;

@end
.m文件:
* * * * * * * * * * * * * * * * * * * * * * * *
#import "FLTransitionAnimation.h"
@import UIKit;

@interface FLTransitionAnimation () 

@end

@implementation FLTransitionAnimation
// 动画执行的时间
static const CGFloat duration = 0.3;
/**
 *  @author 孔凡列, 16-09-02 04:09:30
 *
 *  返回动画执行的时间
 *
 *  @param transitionContext 实现动画效果时可以从参数transitionContext中获取到切换时的上下文信息,比方说从哪个VC切换到哪个VC等
 *
 *  @return return value description
 */
- (NSTimeInterval)transitionDuration:(id)transitionContext{
    return duration;
}

/**
 *  @author 孔凡列, 16-09-02 04:09:25
 *
 *  执行具体动画
 *
 *  @param transitionContext 上下文信息
 */
- (void)animateTransition:(id)transitionContext{
    
    if (self.fl_transitionAnimationType == FLTransitionAnimationTypePresent) {
        // 1.get toView
        UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
        // 2.change frame
        __block CGRect tempFrame = toView.frame;
        if (self.fl_presentDirectionType == FLPresentDirectionTypeFromRight) {
            tempFrame.origin.x = toView.frame.size.width;
        }
        else if (self.fl_presentDirectionType == FLPresentDirectionTypeFromLeft) {
            tempFrame.origin.x = -toView.frame.size.width;
        }
        toView.frame = tempFrame;
        // 3.begin animation
        [UIView animateWithDuration:duration animations:^{
            tempFrame.origin.x = 0;
            toView.frame = tempFrame;
        } completion:^(BOOL finished) {
            // 4.Tell context that we completed
            [transitionContext completeTransition:YES];
        }];
    }
    else if (self.fl_transitionAnimationType == FLTransitionAnimationTypeDismiss){
        // 1.get toView
        UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
        // 2.change frame
        __block CGRect tempFrame = fromView.frame;
        // 3.begin animation
        [UIView animateWithDuration:duration animations:^{
            if (self.fl_dismissDirectionType == FLDismissDirectionTypeFromRight) {
                tempFrame.origin.x = -fromView.frame.size.width;
            }
            else if (self.fl_dismissDirectionType == FLDismissDirectionTypeFromLeft) {
                tempFrame.origin.x = fromView.frame.size.width;
            }
            fromView.frame = tempFrame;
        } completion:^(BOOL finished) {
            // 4.Tell context that we completed
            [transitionContext completeTransition:YES];
        }];
    }
}

@end
  • 2.上文中说到,实现UIViewControllerTransitioningDelegate 的代理方法一般用一个类来实现并管理,FLTransitionManager 就是专门来实现这个协议的代理方法的,比如present 或者 dismiss 是以什么动画进行的,在m文件中遵循 UIViewControllerTransitioningDelegate 协议 并实现其代理方法,告诉它present 和 dismiss 需要哪个动画实例
.h 文件
* * * * * * * * * * * * * * * * * * * * * * * *
#import 
@import UIKit;
// present direction
typedef enum{
    FLPresentTypeFromLeft,
    FLPresentTypeFromRight
}FLPresentType;

// present direction
typedef enum{
    FLDismissTypeFromLeft,
    FLDismissTypeFromRight
}FLDismissType;

@interface FLTransitionManager : NSObject

/**
 *  @author 孔凡列, 16-09-02 04:09:56
 *
 *  present direction
 */
@property (nonatomic,assign)FLPresentType fl_presentType;
/**
 *  @author 孔凡列, 16-09-02 04:09:05
 *
 *  dismiss direction
 */
@property (nonatomic,assign)FLDismissType fl_dismissType;

// 创建一个单例实例

+ (instancetype)shareManager;

@end
.m 文件
* * * * * * * * * * * * * * * * * * * * * * * *
#import "FLTransitionManager.h"
// 具体动画实例
#import "FLTransitionAnimation.h"

@interface FLTransitionManager ()

@end

@implementation FLTransitionManager

static FLTransitionManager *_instance = nil;
+ (instancetype)shareManager{
    if (!_instance) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _instance = [[self alloc] init];
            // 设置默认值
            _instance.fl_presentType = FLPresentTypeFromRight;
            _instance.fl_dismissType = FLDismissTypeFromLeft;
        });
    }
    return _instance;
}

/**
 *  @author 孔凡列, 16-09-02 05:09:10
 *
 *  present 调用
 *
 *  @param presented  被 present 的控制器
 *  @param presenting 正在 present 的控制器
 *  @param source     The view controller whose presentViewController:animated:completion: method was called. 就是调present那个控制器
 *
 *  @return 返回一个动画实例
 */
- (id )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
    FLTransitionAnimation *presentA = [[FLTransitionAnimation alloc] init];
    presentA.fl_transitionAnimationType = FLTransitionAnimationTypePresent;
    if (self.fl_presentType == FLPresentTypeFromLeft) {
        presentA.fl_presentDirectionType = FLPresentDirectionTypeFromLeft;
    }
    else{
        presentA.fl_presentDirectionType = FLPresentDirectionTypeFromRight;
    }
    return presentA;
}

/**
 *  @author 孔凡列, 16-09-02 05:09:20
 *
 *  dismiss 调用
 *
 *  @param dismissed 要dismiss的控制器
 *
 *  @return 返回一个动画实例
 */
- (id )animationControllerForDismissedController:(UIViewController *)dismissed{
    FLTransitionAnimation *dismissA = [[FLTransitionAnimation alloc] init];
    dismissA.fl_transitionAnimationType = FLTransitionAnimationTypeDismiss;
    if (self.fl_dismissType == FLDismissTypeFromLeft) {
        dismissA.fl_dismissDirectionType = FLDismissDirectionTypeFromLeft;
    }
    else if (self.fl_dismissType == FLDismissTypeFromRight){
        dismissA.fl_dismissDirectionType = FLDismissDirectionTypeFromRight;
    }
    return dismissA;
}
@end
  • 3.此时设置 modalPresentationStyle 为 Custom 并且设置transitioningDelegate 为上面创建的管理类FLTransitionManager对象,然后调用present 方法 是没任何反应的,因为我告诉系统,我要自定义这个present,那么系统就不帮你管理了(个人看法,如果我理解错了,望纠正~~),那么我就需要给系统一个管理控制器之间跳转以及特效 的一个控制器,iOS 8 之后,系统提供一个API 就是返回这么一个控制器的 ,这个是UIViewControllerTransitioningDelegate 的一个代理方法,返回一个UIPresentationController 实例,需要继承自定义,然后告诉它要被present的view,这样才能present喔 那么iOS7该怎么处理呢?传送门
.h 文件
* * * * * * * * * * * * * * * * * * * * * * * *
#import 

@interface FLPresentationController : UIPresentationController

@end

* * * * * * * * * * * * * * * * * * * * * * * *
.m文件

#import "FLPresentationController.h"

@implementation FLPresentationController{
    // cover view
    UIView *coverView;
}
/**
 *  @author 孔凡列, 16-09-02 06:09:59
 *
 *  重写构造方法,添加效果
 *
 *  @param presentedViewController  被present 的控制器
 *  @param presentingViewController 正在present 的控制器
 *
 *  @return return value description
 */
- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController{
    if (self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController]) {
        // 添加一些特效什么的~~~这里简单添加了一个遮盖的view,简单做效果演示
        coverView = [[UIView alloc] init];
        coverView.backgroundColor = [UIColor redColor];
        coverView.alpha = 0.0;
    }
    return self;
}

/**
 *  @author 孔凡列, 16-09-02 05:09:36
 *
 *  准备present
 */
- (void)presentationTransitionWillBegin{
    NSLog(@"present will begin");
   // 简单添加效果
    coverView.frame = self.containerView.bounds;
    [self.containerView addSubview:coverView];
    
    // 最重要的 最重要的 最重要的 添加要present的view到容器里面,不然无法present,因为系统都不知道present什么,而且一定要最后添加
    [self.containerView addSubview:self.presentedView];
    
    id coordinator = self.presentingViewController.transitionCoordinator;
    
    [coordinator animateAlongsideTransition:^(id context) {
        coverView.alpha = 1;
    } completion:nil];
}
/**
 *  @author 孔凡列, 16-09-02 06:09:12
 *
 *  present 结束
 *
 *  @param completed completed description
 */
- (void)presentationTransitionDidEnd:(BOOL)completed{
    NSLog(@"present did end");
    if(!completed){
        [coverView removeFromSuperview];
    }
}
/**
 *  @author 孔凡列, 16-09-02 06:09:15
 *
 *  准备 dismiss
 */
- (void)dismissalTransitionWillBegin{
    
    NSLog(@"dismiss will begin");
    id coordinator = self.presentingViewController.transitionCoordinator;
    
    [coordinator animateAlongsideTransition:^(id context) {
        coverView.alpha = 0.0;
    } completion:nil];
}
/**
 *  @author 孔凡列, 16-09-02 06:09:18
 *
 *  dismiss 结束
 *
 *  @param completed completed description
 */
- (void)dismissalTransitionDidEnd:(BOOL)completed{
    NSLog(@"dismiss did end");
    if(completed){
        [coverView removeFromSuperview];
    }
}
  • 4.此时在FLTransitionManager 的 m文件中实现 presentationControllerForPresentedViewController 这个代理方法,返回自定义的FLPresentationController 就OK
/**
 *  @author 孔凡列, 16-09-02 05:09:19
 *
 *  返回一个 用来控制 controller 之间的跳转特效 的控制器 (注意此API是 iOS8之后才有)
 *
 *  @param presented  被 present 的控制器
 *  @param presenting 正在 present 的控制器
 *  @param source     谁调present
 *
 *  @return 返回一个 用来控制 controller 之间的跳转特效 的控制器
 */
- (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source NS_AVAILABLE_IOS(8_0){
    return [[FLPresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting];
}
  • 5.此时终于可以调用了,调用只需要比平时present多几句代码~~
    SecondViewController  *vc = [[SecondViewController alloc] init];
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
    
    nav.modalPresentationStyle = UIModalPresentationCustom;
    nav.transitioningDelegate = [FLTransitionManager shareManager];
    
    [self presentViewController:nav animated:YES completion:nil];


    dismiss 直接调,什么都不用设置
    [self dismissViewControllerAnimated:YES completion:nil];
  • 6.最后附上效果图~~~哈哈,辛苦啦!
自定义Modal转场-模仿push & pop_第2张图片
2.gif

总结来了!

  • 其实没啥好说的,展望一下未来吧~~
    • 上面实现的是动画式切换,还有一种是交互式切换,给你一张图看看
自定义Modal转场-模仿push & pop_第3张图片
iOS 7中视图控制器切换.png

恩,还不够清楚!参考这个-->iOS7之定制View Controller切换效果

系统的pop 就是交互式切换的,因此,上面的还不算高仿,入栈这个不搞了,往后就先弄个拖拽手势~期待ing

你可能感兴趣的:(自定义Modal转场-模仿push & pop)