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

前言:

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

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

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 <Foundation/Foundation.h>

// 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 () <UIViewControllerAnimatedTransitioning>

@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<UIViewControllerContextTransitioning>)transitionContext{
    return duration;
}

/**
 *  @author 孔凡列, 16-09-02 04:09:25
 *
 *  执行具体动画
 *
 *  @param transitionContext 上下文信息
 */
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)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 <Foundation/Foundation.h>
@import UIKit;
// present direction
typedef enum{
    FLPresentTypeFromLeft,
    FLPresentTypeFromRight
}FLPresentType;

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

@interface FLTransitionManager : NSObject<UIViewControllerTransitioningDelegate>

/**
 *  @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 <UIKit/UIKit.h>

@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<UIViewControllerTransitionCoordinator> coordinator = self.presentingViewController.transitionCoordinator;
    
    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> 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<UIViewControllerTransitionCoordinator> coordinator = self.presentingViewController.transitionCoordinator;
    
    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> 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.最后附上效果图~~~哈哈,辛苦啦!
2.gif

总结来了!

  • 其实没啥好说的,展望一下未来吧~~
    • 上面实现的是动画式切换,还有一种是交互式切换,给你一张图看看
iOS 7中视图控制器切换.png

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容