iOS学笔记本46-iOS自定义转场

转载 2015年11月20日 10:07:09
本文是iOS自定义视图控制器转场系列的第一篇。本文重点在于创建自定义动画(非交互式)转场。

当使用传统的iOS应用程序时,我们经常在视图间转场。过去,如果你不想用标准的转场动画,全靠你自己,但在iOS 7中苹果提供了一个新的API让我们自定义这些动画。

iOS提供了一些内置的转场类型。Navigation controllers用push和pop来有层次地导航信息,tab bar controllers用切换tabs来在各部分之间跳转,所有的视图控制器可以根据特定任务模态化地present和dismiss另一个视图控制器。

API介绍

  • 每一个自定义转场涉及三个主要对象:

  • from view controller (消失的那个)

  • to view controller (出现的那个)

  • 一个动画控制器

自定义转场和在自定义之前一样。对于push和pop,意味着调用UINavigationController的push-、pop-、或者set-方法来修改视图控制器的堆栈。对于切换tabs,意味着修改UITabBarController的selectedIndex或selectedViewController属性。对于modal,则意味着调用?[UIViewController presentViewController: animated: completion: ]或?[UIViewController dismissViewControllerAnimated: completion: ]。无论哪种情况,这个步骤都确定了“from view controller”和“to view controller”。

使用一个自定义转场,你需要一个动画控制器。对我来说这是自定义动画转场中最令人困惑的部分,因为每种转场需要的动画控制器不同。下表展示了如何为每种转场提供动画控制器。记着,委托方法总是返回动画控制器。

65.png

动画控制器可以是任何遵守UIViewControllerAnimatedTransitioning协议的对象。该协议声明了两个必须要实现的方法。一个提供了动画的时间,另一个执行了动画。这些方法调用时都传递一个上下文。上下文提供了入口来访问信息和你创建自定义转场需要的对象。以下是一些重点:

  • from view controller

  • to view controller

  • 两个视图控制器view的第一帧和最后一帧

  • container view,根据这篇文档,“作为的转场中视图的父视图”

重要:上下文还实现了-completeTransition:,你必须在你自定义转场结束时调用一次。

这是关于自定义转场所有你需要知道的。让我们来看一些例子!

例子

所有这些例子都可以在GitHub找到,你可以克隆这些仓库,然后边往下看边试试这些例子。

这三个例子都直接或子类化地使用了TWTExampleViewController。它只是设置了视图的背景颜色,同时使你能够通过点击任何地方来结束例子回到主菜单。

轻弹push和pop

在这个例子中,目标是让push和pop使用flip动画而不是标准的slide动画。一开始我建立一个navigation controller并把TWTPushExampleViewController的实例当作root。TWTPushExampleViewController添加了一个叫“Push”的右按钮到导航栏。点击它时,一个新的TWTPushExampleViewController的实例被压入navigation的堆栈:

1
2
3
4
5
6
- (void)pushButtonTapped
{
    TWTPushExampleViewController *viewController = [[TWTPushExampleViewController alloc] init];
    viewController.delegate = self.delegate;
    [self.navigationController pushViewController:viewController animated:YES];
}

navigation controller的设置发生在TWTExamplesListViewController(运行demo主菜单的视图控制器)。注意,它把自己置为navigation controller的委托:

1
2
3
4
5
6
7
8
9
10
- (void)presentPushExample
{
    TWTPushExampleViewController *viewController = [[TWTPushExampleViewController alloc] init];
    viewController.delegate = self;
  
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
    navigationController.delegate = self;
  
    [self presentViewController:navigationController animated:YES completion:nil];
}

这意味着当navigation controller的转场即将开始时,TWTExamplesListViewController将收到委托信息,并有机会返回一个动画控制器。对于这种转场,我使用一个TWTSimpleAnimationController的实例,它是一个+[UIView transitionFromView: toView: duration: options: completion:]的封装:

1
2
3
4
5
6
7
8
9
10
11
12
- (id)navigationController:(UINavigationController *)navigationController
                                  animationControllerForOperation:(UINavigationControllerOperation)operation
                                               fromViewController:(UIViewController *)fromVC
                                                 toViewController:(UIViewController *)toVC
{
    TWTSimpleAnimationController *animationController = [[TWTSimpleAnimationController alloc] init];
    animationController.duration = 0.5;
    animationController.options = (  operation == UINavigationControllerOperationPush
                                   ? UIViewAnimationOptionTransitionFlipFromRight
                                   : UIViewAnimationOptionTransitionFlipFromLeft);
    return animationController;
}

如果转场是一个push,我使用一个从右侧的flip,否则,我使用一个从左侧的flip。

以下是TWTSimpleAnimationController的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (NSTimeInterval)transitionDuration:(id)transitionContext
{
    return self.duration;
}
  
- (void)animateTransition:(id)transitionContext
{
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
    [toViewController.view layoutIfNeeded];
  
    [UIView transitionFromView:fromViewController.view
                        toView:toViewController.view
                      duration:self.duration
                       options:self.options
                    completion:^(BOOL finished) {
                        [transitionContext completeTransition:YES];
                    }];
}

记着,这两个方法是UIViewControllerAnimatedTransitioning协议的一部分。在动画控制器运行自定义转场的时候,它们被UIKit调用。

这里有一些关于animateTransition:需要注意的事情:

  • from view controller,to view controller,以及to view controller的最后一帧都从转场的上下文中提取。其中还有一些其他可提取的信息,但在当前情况下,并不需要所有信息。

  • +[UIView transitionFromView: toView: duration: options: completion:]负责有层次地添加和删除视图。在后面的例子中,我将展示一种手动完成的情况。

  • 在转场的completion代码块中,我调用[transitionContext completeTransition: YES]来告诉系统转场结束了。如果你忘了这样做,你将无法与app交互。如果出现这种情况,先检查这个原因。

以上就是全部!现在有一些值得尝试的东西:

  • 改变动画的持续时间来看看它如何影响navigation bar的动画。

  • 把动画选项由flip改为page curls。

  • 找一个方法让navigation的堆栈中的每个视图控制器能指定自己的动画控制器。看看在本文最后的推荐模式中提出的方法。

淡入淡出切换tabs

这个例子应该很熟悉。它使用和之前相同的观点,但使用tab bar controller而不是navigation controller。

以下是TWTExamplesListViewController的设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)presentTabsExample
{
    NSMutableArray *viewControllers = [[NSMutableArray alloc] init];
  
    for (NSUInteger i=0; i<3; i++) {
        TWTChangingTabsExampleViewController *viewController = [[TWTChangingTabsExampleViewController alloc] init];
        viewController.delegate = self;
        viewController.index = i;
        [viewControllers addObject:viewController];
    }
  
    UITabBarController *tabBarController = [[UITabBarController alloc] init];
    [tabBarController setViewControllers:viewControllers animated:NO];
    tabBarController.delegate = self;
  
    [self presentViewController:tabBarController animated:YES completion:nil];
}

TWTChangingTabsExampleViewController的index只是一个为每个视图控制器自定义标签的方法。就像之前一样,TWTExamplesListViewController是委托,它将在切换tabs的时候提供动画控制器:

1
2
3
4
5
6
7
8
9
- (id)tabBarController:(UITabBarController *)tabBarController
           animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                             toViewController:(UIViewController *)toVC
{
    TWTSimpleAnimationController *animationController = [[TWTSimpleAnimationController alloc] init];
    animationController.duration = 0.5;
    animationController.options = UIViewAnimationOptionTransitionCrossDissolve;
    return animationController;
}

看着熟悉吗?这就够了。

弹出一个覆盖视图

我最喜欢自定义转场的一个用途,是它可以弹出覆盖式的视图控制器。以前,如果你想模仿一个社会化分享菜单,或任何需要呈现视图控制器的同时保持背后内容的可见,你不得不从许多不幸的选项中做出选择(直接添加视图到窗口是我见过的最通用的方法)。然而幸运的是,这不再是必要的。

能这样用的关键原因,是当被弹出视图控制器的-modalPresentationStyle被置为UIModalPresentationCustom时,弹出视图控制器就不会自动从视图层中删除。为了弹出一个覆盖视图,它仅仅如同是离开一般,这样被弹出的视图就可以显示在它上方。

以下是TWTExamplesListViewController的设置:

1
2
3
4
5
6
7
8
9
- (void)presentPresentExample
{
    TWTOverlayExampleViewController *viewController = [[TWTOverlayExampleViewController alloc] init];
    viewController.delegate = self;
    viewController.modalPresentationStyle = UIModalPresentationCustom;
    viewController.transitioningDelegate = self;
  
    [self presentViewController:viewController animated:YES completion:nil];
}

这里两个要注意的事情是:modalPresentationStyle被置为UIModalPresentationCustom,被弹出视图控制器的-transitioningDelegate被置为TWTExamplesListViewController。当转场开始时,TWTExamplesListViewController收到转场的委托信息:

1
2
3
4
5
6
- (id)animationControllerForPresentedController:(UIViewController *)presented
                                                                  presentingController:(UIViewController *)presenting
                                                                      sourceController:(UIViewController *)source
{
    return [[TWTPresentAnimationController alloc] init];
}

如你所见,我创建了一个自定义动画控制器类作为示例。我将要关注-animateTransition:的实现,因为它与之前的部分并不相同。

开始前,我用transitionContext提取toViewController(被弹出视图控制器)和containerView(容器视图)。

1
2
3
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
 
UIView *containerView = [transitionContext containerView];

containerView在转场中是from view controller和to view controller的父视图。最初,to view controller的视图没有被添加到containerView,它的frame也未确定。我这里直接赋值frame,你也可以使用auto layout。

1
2
3
4
5
6
CGRect frame = containerView.bounds;
frame = UIEdgeInsetsInsetRect(frame, UIEdgeInsetsMake(40.0, 40.0, 200.0, 40.0));
  
toViewController.view.frame = frame;
  
[containerView addSubview:toViewController.view];

对于这种转场,我希望这个视图pop到屏幕上。要做到这一点,我用UIView的spring动画把视图的scale从0.3变化到1。

说句题外话,若动画中scale从大于0的值开始同时alpha从0到1,则可以让你动画速度比简单的scale从0到1更快。试着删除alpha动画,再和从0开始的scale动画比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
toViewController.view.alpha = 0.0;
toViewController.view.transform = CGAffineTransformMakeScale(0.3, 0.3);
  
NSTimeInterval duration = [self transitionDuration:transitionContext];
  
[UIView animateWithDuration:duration / 2.0 animations:^{
    toViewController.view.alpha = 1.0;
}];
  
CGFloat damping = 0.55;
  
[UIView animateWithDuration:duration delay:0.0 usingSpringWithDamping:damping initialSpringVelocity:1.0 / damping options:0 animations:^{
    toViewController.view.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
    [transitionContext completeTransition:YES];
}];

在completion块,我调用-completeTransition:,到这里就完了!现在到了弹回...

在弹回开始时,TWTExamplesListViewController收到转场的委托信息:

1
2
3
4
- (id)animationControllerForDismissedController:(UIViewController *)dismissed
{
    return [[TWTDismissAnimationController alloc] init];
}

返回另一个自定义动画控制器。让我们看一看-animateTransition::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
  
NSTimeInterval duration = [self transitionDuration:transitionContext];
  
[UIView animateWithDuration:3.0 * duration / 4.0
                      delay:duration / 4.0
                    options:UIViewAnimationOptionCurveEaseIn
                 animations:^{
                     fromViewController.view.alpha = 0.0;
                 }
                 completion:^(BOOL finished) {
                     [fromViewController.view removeFromSuperview];
                     [transitionContext completeTransition:YES];
                 }];
  
[UIView animateWithDuration:2.0 * duration
                      delay:0.0
     usingSpringWithDamping:1.0
      initialSpringVelocity:-15.0
                    options:0
                 animations:^{
                     fromViewController.view.transform = CGAffineTransformMakeScale(0.3, 0.3);
                 }
                 completion:nil];

如你所见,这个想法是倒转弹出动画。需要注意的重要区别是,from view controller的视图在转场结束前就从视图层中被删除了。

在继续之前,试着改变一下弹回的动画。

推荐模式

到这里,您可能已经注意到这个API在很大程度上依赖于协议和委托方法。因此很容易把自定义转场编写得混乱且无法重用。这里有一些模式帮助我保持整洁:

1.用专门的对象作动画控制器

虽然一个视图控制器可以直接代码量翻倍当作动画控制器,但几乎可以肯定这样做会减少自定义转场的可重用性。此外,视图控制器已经因为做得太多而臭名昭著。你可以创建可重用的动画控制器,只需创建NSObject子类,遵守UIViewControllerAnimatedTransitioning协议,然后在需要的时候返回它们的实例。

我们在Toast里的TWTSimpleAnimationController就是这样做的,它很容易重用和用CocoaPod集成。

2.不要让UINavigationControllerDelegate需要知道转场的细节

用navigation controller的委托返回动画控制器是好,如果你想让所有的push和pop转场都以同样的方式进行。然而在某些情况下,你可能希望只自定义一个push或pop。

一种混乱的方式是把navigation controller的委托对象暂时改变成一个知道这种转场的对象。另一种混乱的方式是使navigation controlle的委托根据特定的from view controllers和to view controllers有逻辑地去确定。显然,这些都不是好的选择。

用模式可以干净利落地解决这个问题,只要向UIViewController添加-pushAnimationController和-popAnimationController属性。然后navigation controller的委托就可以返回由被push的动画控制器和被pop视图控制器指定的动画控制器。这使得navigation controller的委托保持通用同时避免了委托对象的改变。TWTNavigationControllerDelegate实现了这种模式,它也同时包括在Toast里。

这样就结束了这个创建自定义动画视图控制器转场的介绍。一定要看看我们的在Toast里的视图控制器转场模块,并继续关注第二部分,该部分将包括自定义交互式转场

相关文章推荐

iOS自定义转场

  • 2016-04-19 15:36
  • 81KB
  • 下载

iOS-自定义转场动画

  • 2017-06-28 18:30
  • 51KB
  • 下载

IOS 自定义转场动画。

以前对IOS 的专场动画一直不敢去动自定义动画(非交互式) 怕自己搞不好,最近无事就突突一下。发现没有想象的那么难!(大神就莫见笑。纯属自我学习) 储备只是 简单UIView动画。仅此而已, 能看懂 ...

ios 自定义转场动画

  • 2015-01-26 17:48
  • 121KB
  • 下载

iOS自定义转场动画

在iOS程序里出现得最多的转场动画,就是UINavigationController的Push和Pop了,看多了就觉得有些无聊了,还好苹果提供了自定义转场动画的API,往下看。 首页,要明白既然转场动...
  • hmxhh
  • hmxhh
  • 2014-11-26 14:08
  • 1490

VCTransitionsLibrary –自定义iOS交互式转场动画的库

VCTransitionsLibrary 提供了许多适用于入栈,出栈,模态等场景下控制器切换时的转场动画.它本身提供了一个定义好的转场动画库,你可以拖到自己工程中直接使用;也提供了许多拥有不同转场动画...

iOS自定义转场动画

如果你在开发中遇到需求,需要实现各种各样的转场动画,那么你可以看看这篇文章。当然,本文并没有实现各种各样的花式转场动画,而是实现了一种思路,抛砖引玉,希望你在看了本文之后能举一反三,随心所欲的定制自己...
  • dhfsh
  • dhfsh
  • 2016-08-23 14:50
  • 69

iOS7教程系列:自定义导航转场动画以及更多

原文:iOS 7 Tutorial Series: Custom Navigation Transitions & More 感谢翻译小组成员dingdaojun热心翻译。本篇文章是...

iOS自定义Modal视图控制器的转场动画

iOS开发中,当Modal出一个视图控制器(现在苹果官方叫present)的时候,默认的动画是从屏幕底部钻出你Modal出来的视图控制器视图,但是很多时候我们想要不同的动画效果,比如从中间放大或者从其...

iOS 7:自定义导航转场动画以及更多

在iOS7以前,开发者如果希望定制导航控制器推入推出视图时的转场动画,一般都只能通过子类化UINavigationController或者自己编写动画代码去覆盖相应的方法,现在iOS7为开发者带来了福...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)