自定义modal动画

来源:互联网 发布:packetsender mac 编辑:程序博客网 时间:2024/06/17 07:54

modal界面跳转

在IOS中,相比大家都不陌生,除了导航控制器中的经常使用的push以外,我们也经常使用从下而上跳转的控制器效果,这也就是modal效果。

modal跳转使用

对于使用storyBoard内进行连线的跳转,我们只需要将一个按钮连线到我们需要的控制器,然后选择modal效果,就可以实现从当前控制器跳转到下一个控制器。

而如何通过代码的话,也十分容易,先准备好需要跳转的modalVC控制器,只需要调用这么一句话

[self presentViewController:modalVC animated:YES completion:completion];

然后当需要回到原先界面的时候,只需要在出发时间的方法内部调用

[self.dismissViewControllerAnimated:YES completion:nil];

modal扩展需求

一般而言,我们对于界面的跳转也就到这里为止了,所以我们可以看到目前大部分的APP在采用modal跳转的时候都是很简单的从下往上跳转,然后回去从下往上。

但是当我们闲着蛋疼看一些酷炫的APP的时候,会发现有些界面的跳转十分的酷炫。

  • 非常规跳转动画,也就是在modal的效果上,转换成其他的一些转场动画,或者自己定义一些效果
  • 让跳出来的界面不是全屏显示,并且可以随意的动画跳出来,也就是类似下面的这样的效果。这是我从GitHub Pod大神封装的Pop动画演示程序截图的,
    https://github.com/schneiderandre/popping
    pop效果

modal系统默认情况下的调用原理

  • 对于StoryBoard拖线实现的跳转来说,和其他控制器跳转一样,跳转的前后的视图控制器都是通过Segue对象来进行的。Segue会在跳转前调用这个方法
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

根据Segue对象的destinationViewController属性可以获得跳转目标控制器,一般我们会该方法做跳转前的一些动作。
* 而对于用代码跳转的方式,因为是我们自己创建的目标控制器,因此,我们可以自己手动的在执行跳转的presentViewController方法之前做好需要做的准备。
* 其实不论是StoryBoard还是代码,系统最终都会调用presentViewController进行跳转。

内部系统提供的方法

  • 在modal跳转的时候,其实每个需要被modal的视图控制器都有两个属性:
    • modalPresentationStyle modal展现的类型。这是一个枚举类型,官方头文件中表示了内部的全部类型:
typedef NS_ENUM(NSInteger, UIModalPresentationStyle) {        UIModalPresentationFullScreen = 0,        UIModalPresentationPageSheet NS_ENUM_AVAILABLE_IOS(3_2),        UIModalPresentationFormSheet NS_ENUM_AVAILABLE_IOS(3_2),        UIModalPresentationCurrentContext NS_ENUM_AVAILABLE_IOS(3_2),        UIModalPresentationCustom NS_ENUM_AVAILABLE_IOS(7_0),        UIModalPresentationOverFullScreen NS_ENUM_AVAILABLE_IOS(8_0),        UIModalPresentationOverCurrentContext NS_ENUM_AVAILABLE_IOS(8_0),        UIModalPresentationPopover NS_ENUM_AVAILABLE_IOS(8_0),        UIModalPresentationNone NS_ENUM_AVAILABLE_IOS(7_0) = -1,         };

从这些类型中,我们很容易就可以发现,其实系统本身也支持其他的一些展示的效果,不过效果有些局限。但是IOS 7.0之后,我们发现这里多了一个UIModalPresentationCustom。也就是说,我们可以自定义展现类型咯。
既然可以自定义,系统的其他类型就不管了,只要能实现自定义,那么大千世界就任我为所欲为咯。

  • transitioningDelegate 动画代理。 它是一个遵守了UIViewControllerTransitioningDelegate 协议的任意对象。那么既然是协议,我们就先看看内部有哪些协议方法:
// 控制展现动画的控制器,该方法要求返回遵守了UIViewControllerAnimatedTransitioning该协议的对象- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;// 控制消失动画的控制器,该方法要求返回遵守了UIViewControllerAnimatedTransitioning该协议的对象- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;// 返回控制转场动画的控制器(UIPresentationController),该方法将正在显示的对象,和需要显示的对象都交给你,然后返回一个控制转场动画的动画- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source NS_AVAILABLE_IOS(8_0);

这个协议中还有另外两个是支持实时交互的方法,这里暂时涉及不到就先不提了。

从代理方法我们可以看出,根据上面的两个方法,我们还需要了解

UIViewControllerAnimatedTransitioning这个协议// 转场动画 (提供了转场上下文,根据上下文处理转场动画)- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;// 转场动画时长,一般这里都随便填,真正的动画时间看你自定义的动画时长来决定- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext;

自定义modal分析

根据以上的代理方法,根据系统的调用顺序,合理的实现代理方法就可以进行自定义动画了。

分析图

根据分析图可知,系统会根据系统内部顺序调用代理方法,因此,我们需要继承以上代理并且实现内部的代理方法。

专门设置一个动画代理

为了防止主控制器的臃肿和方法的解耦,主控制不关心动画的过程,而是我们专门创建一个对象来遵守所有的协议方法并且实现协议的方法。该代理遵守转场代理和转场动画协议,所有的转场动画的协议方法全部通过他实现

每当我们需要进行跳转的时候,我们只需要给需要跳转的控制器的转场代理和转场方式,并且创建一个动画代理实现代理方法,在转场动画的代理方法中实现不同的动画即可根据自定义的方法进行跳转了。

思路扩展

如果一个项目需要比较多的modal跳转,或者需要酷炫的自定义modal跳转。如果每次都要自己去根据上面的思路图实现各种协议方法是一件十分费力的事情。
由于上面分解出来了一个动画代理,因此该代理就可以进行封装和重用。

封装框架

目标:

控制器提供需要跳转的控制器,提供跳转控制器显示的大小位置(frame),根据提供的显示动画和消失动画即可实现自定义跳转。

目的:

完全不关心内部代理方法的实现,系统调用的方式,只需要提供自定义的内容来完成自定义跳转。

实现

做一个ViewController的分类,提供类似跳转的方法。

/** *  自定义modal的跳转方式 * *  @param modalVC          需要展示的viewController *  @param presentFrame     展示视图在屏幕的frame *  @param presentAnimation 展示动画代码(返回的时间是转场动画上下文关闭的时间) *  @param dismissAnimation 消失动画代码(返回的时间是转场动画上下文关闭的时间) 关于转场动画上下文时长说明: 转场动画上下文关闭的时间决定了改转场动画封锁界面的用户交互能力的时长,如果返回0表示立马接受用户交互, 那么可能存在在动画过程中用户交互而导致动画达不到预期效果。 一般建议返回动画的时间长度,正好动画结束,然后开启用户交互能力。 特殊需求可以填写特殊时长 *  @param completion       完成回调 */-(void)mk_presentViewController:(UIViewController *)modalVC               withPresentFrame:(CGRect)presentFrame           withPresentAnimation:(NSTimeInterval (^)(UIView *view))presentAnimation           withDismissAnimation:(NSTimeInterval (^)(UIView *view))dismissAnimation                 withCompletion:(void (^)(void))completion;

再通过运行时动态添加一个属性用于存储执行动画的代理对象

const void *animationDelegateKey = "animationDelegate";/** *  runtime动态加载执行动画的代理属性的set方法 */- (void)setAnimationDelegate:(ZJAnimationDelegate *)animationDelegate {    objc_setAssociatedObject(self, animationDelegateKey, animationDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}/** *  runtime动态加载执行动画的代理属性的get方法 */- (ZJAnimationDelegate *)animationDelegate {    return objc_getAssociatedObject(self, animationDelegateKey);}

代理对象内部存储了跳转需要的动画block,在动画方法内部在恰当的时间调用动画。

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{    if (self.isPresenting) {        UIView *view = [transitionContext viewForKey:UITransitionContextToViewKey];        [[transitionContext containerView] addSubview:view];        NSTimeInterval time = self.presentAnimation(view);        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{            [transitionContext completeTransition:YES];        });    }else {        UIView *view = [transitionContext viewForKey:UITransitionContextFromViewKey];        NSTimeInterval time = self.dismissAnimation(view);        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{            [transitionContext completeTransition:YES];            [view removeFromSuperview];        });    }}

注意事项

[transitionContext completeTransition:YES];
一定要在动画结束之后调用,不然系统任务转场动画没结束,会一直封锁整个界面和用户教育的能力。

所以这句话我是采用用户返回的时间来延迟加载。这句话只要在动画开始之后执行都不影响动画的执行效果,但是如果在动画之前执行会提前开启用户交互能力,因此可能动画会被用户的其他操作而打断。

GitHub地址

https://github.com/ZJJeffery/ZJModalKing

粗略的封装了一个框架用于该需求。
pod上搜索 pod search ZJModalKing即可安装。

0 0