前言
因项目需要,最近一段时间都在调研iOS的过场动画。对于我来说这是一个之前没有太涉及的领域,所以有必要把调研的过程和自己的一些理解纪录下来
为什么要自定义过场动画?
如果大家有关注Material Design和最近一些知名App(如快的、一号专车等)的界面设计和交互的变化,就会发现一种新的趋势:平滑的页面过渡。目的旨在于让用户尽量少地感觉到页面之间生硬的切换,从而使App的体验更加流畅。而iOS原生的两种常用过场:Push/Pop和Present,和目前流行的趋势显然是不太符合的,所以自定义过场动画的意义就体现出来了。
Transition-iOS的自定义过场
简介
因为之前有博客对自定义过场做了非常详细的介绍,我就不赘述了,具体参照这里 iOS7之定制View Controller切换效果 (PS:感谢作者)。作者的demo我也有下载看过,他是为每个过场动画封装了单独的类,然后在UIViewController中实现过场切换的代理,在代理中返回相应的动画效果。对于为过场动画封装单独的类这点我是非常赞同的,但是在UIViewController中实现过场切换的代理这一点我觉得不是特别理想,所以后来我的实现做了修改,最终的效果是在UIViewController中只需要调用一个接口,就可以实现自定义过场的效果。
我的设计
分析
首先,我封装了一个单例模式MBTransition基类,使用单例模式的原因有两个:
- 在一个App中,同时存在的过场只会有一个。
- 实现成单例之后过场对象就不需要依赖于某个UIViewController。
然后.m文件中为这个类实现过场动画的几个代理
#pragma mark UINavigationControllerDelegate methods
// Push/Pop时自定义过场的代理
// 参数:
// navigationController:导航
// operation:导航的操作:Push/Pop/None,可以用来控制在哪种导航的操作下使用自定义过场
// fromVC:执行Push操作的UIViewController
// toVC:被Push的UIViewController
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC {
return self;
}
// Present时自定义过场的代理
// 参数:
// presented:被Present的UIViewController
// presenting:正在执行Present的UIViewController
// source:发起Present的UIViewController(PS:正在执行Present和发起Present的UIViewController是有区别的,如果source是某个UINavigationController下的一个UIViewController,那么presenting就是这个UINavigationController,如果source不是在类似UINavigationController或者UITabbarController这样的控件内,那么presenting就是source本身)
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
return self;
}
// Dismiss时自定义过场的代理
// 参数:
// dismissed:被Dismiss掉的UIViewController
-(id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
return self;
}
#pragma mark - UIViewControllerContextTransitioning
// 实现具体自定义过场动画效果的代理,这个代理也是实现动画效果的核心
// 参数:
// transitionContext:过场时的上下文信息
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
}
// 过场动画时间的代理
// 参数:
// transitionContext:过场时的上下文信息
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return self.duration;
}
通过上面几个代理我们可以知道:Push/Pop和Present时过场动画的代理是不一样的,所以我建立了一个过场类型的枚举,用来控制自定义过场在哪种交互下可用:
typedef enum TransitionType{
TransitionTypePush, // Push/Pop过场
TransitionTypePresent // Present过场
}TransitionType;
然后在- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
代理中我们返回了self.duration
,所以我们需要在.h文件中添加一个变量来保存过场动画持续的时间:
@property (nonatomic, assign) NSTimeInterval duration;
接下来我们分析一下 (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
代理中我们做的一些事情:
- 通过
transitionContext
获取到过场时切换的两个UIViewController// 这里的 fromVC 和 toVC 代表的是过场是由 fromVC 切换到 toVC 的。 // 比如从A界面Push到B界面时,这里的fromVC是A界面,toVC是B界面,而当B界面被Pop到A界面时,这里的fromVC就是B界面,toVC就是A界面 UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
- 通过
transitionContext
获取到执行切换的UIView// 所有的切换动画都是基于container来实现的 UIView *container = [transitionContext containerView];
- 通过
transitionContext
获取到过场的持续时间NSTimeInterval duration = [self transitionDuration:transitionContext];
- 最后通过
transitionContext
获取到过场时切换的 fromVC 和 toVC 和Push/Present时保存的 fromVC 和 toVC 进行比较就可以知道目前执行的是Push/Present还是Pop/Dismiss,从而可以为Push/Present和Pop/Dismiss定制不同的动画效果。- (BOOL)isReversed:(UIViewController *)fromVC ToVC:(UIViewController *)toVC { return !([self.fromVC class] == [fromVC class] && [self.toVC class] == [toVC class]); }
所以我们需要在.h文件中添加两个成员变量来保存Push/Present时的 fromVC 和 toVC :
@property (nonatomic, weak) UIViewController *fromVC; @property (nonatomic, weak) UIViewController *toVC;
- 接下来就是具体的过场动画部分了,其实就是结合fromVC的view、toVC的view和container做一些动画效果,因为跟做普通的动画没有什么区别,所以这个部分我就不具体描述了。
最后是提供给外部调用的接口,内容如下:
- (void)setTransitionWithFromViewController:(UIViewController *)fromVC ToViewController:(UIViewController *)toVC TransitionType:(TransitionType)type Duration:(NSTimeInterval)duration{
self.fromVC = fromVC;
self.toVC = toVC;
self.duration = duration;
if (type == TransitionTypePush) {
self.fromVC.navigationController.delegate = self;
}else if (type == TransitionTypePresent){
self.fromVC.transitioningDelegate = self;
self.toVC.transitioningDelegate = self;
}
}
上面代码片段所做的事情就是对一些参数进行保存,然后根据 TransitionType 来设置相应的代理。
特点
- 如果要实现其他自定义过场,只需要继承MBTransition,然后重写
(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
代理即可。 - 使用者只需调用一个接口即可实现自定义过场,降低了代码耦合。
交互式的切换动画
交互式动画主要是指用户可以通过手势控制整个动画的过程,这个部分我目前还没有研究…
碰到的一些坑
- 当UIViewController是UITabbarController或者UINavigationController的一个childViewController时,通过
[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]
和[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]
拿到的UIViewController其实是UITabbarController或者UINavigationController,所以在调用接口时,要注意传入的 fromVC 和 toVC 其实是这个UIViewController的UITabbarController或者UINavigationController。 - 如果采用Push/Pop的方式,当fromVC属于UITabbarController的一个childViewController,且在 toVC 上不能显示UITabbarController的UITabBar时,UITabbarController的UITabBar会造成很大的麻烦:
- 如果使用设置hidesBottomBarWhenPushed为true的方式,那么UITabBar的动画不能定制,只能是默认的从右到左和从左到右。
- 如果使用自定义的方式显示和隐藏UITabBar,因为AutoLayout的原因,后期问题会更多…
所以在这种情况下建议使用Present的方式切换到新的界面,当然如果大家有好的解决方法也希望能分享给我,谢谢!
版权声明:本文为博主原创文章,未经博主允许不得转载。