2016年4月14日 星期四

[IOS] 自訂切換頁面動畫的方法 with "PresentViewController"

自訂切換頁面動畫的方法,其實很多種方式可以實現

這次我要記錄的是跟“presentViewController”有關
  1. [self.navigationController presentViewController:self.viewCtrl animated:YES completion:NULL];
對!就是上面這個!!

在我原本認知中,這是個一行搞定的簡單函示,但沒想到可以有延伸的應用


我是從這篇 http://jackyeh.logdown.com/posts/240412-ios-custom-viewcontroller-transition 跟其他網路上的資料學習的,主要是網址這篇



首先你可以創個 customPresentAnimation.h 跟 customPresentAnimation.m 

 customPresentAnimation.h 裡
  1. @interface customPresentAnimation : NSObject<UIViewControllerAnimatedTransitioning>

  2. @property (assign, nonatomic )BOOL appearing;

  3. @end
h檔裡這樣就好了appearing 是我拿來判斷是要 出現 還是 消失 用的

有些人會把出現跟消失的動畫分成兩個檔案來寫,但我這個人比較懶惰,所以我決定把他們寫在一起

m檔裡要這些


  1. - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
  2. {
  3.     
  4.     return 1.0;
  5. }

  6. - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
  7. {
  8.     UIViewController *viewControllerA = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
  9.     UIViewController *viewControllerB = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
  10.     UIView *fromView = viewControllerA.view;
  11.     UIView *toView = viewControllerB.view;
  12.     UIView *containerView = [transitionContext containerView];
  13.     NSTimeInterval duration = [self transitionDuration:transitionContext];
  14.     
  15.     CGRect initialFrame = [transitionContext initialFrameForViewController:viewControllerA];
  16.     CGRect offscreenRect = CGRectOffset(initialFrame, 0, -[UIScreen mainScreen].bounds.size.height);
  17.     
  18.     // Presenting
  19.     if (self.appearing) {
  20.         // Position the view offscreen
  21.         toView.frame = offscreenRect;
  22.         [containerView addSubview:toView];
  23.         
  24.         // Animate the view onscreen
  25.         [UIView animateWithDuration:duration
  26.                               delay:0
  27.              usingSpringWithDamping:0.5
  28.               initialSpringVelocity:1
  29.                             options:0
  30.                          animations: ^{
  31.                              toView.frame = initialFrame;
  32.                          } completion: ^(BOOL finished) {
  33.                              [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
  34.                          }];
  35.     }
  36.     // Dismissing
  37.     else {
  38.         [containerView addSubview:toView];
  39.         [containerView sendSubviewToBack:toView];
  40.         
  41.         // Animate the view offscreen
  42.         [UIView animateWithDuration:duration
  43.                               delay:0
  44.              usingSpringWithDamping:0.5
  45.               initialSpringVelocity:1
  46.                             options:0
  47.                          animations: ^{
  48.                              fromView.frame = offscreenRect;
  49.                          } completion: ^(BOOL finished) {
  50.                              [fromView removeFromSuperview];
  51.                              [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
  52.                          }];
  53.     }
  54. }
這裡的m檔 我先用網址裡的範例說明

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext

這個是拿來回傳動畫的時間

接著

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext

這個是要寫兩個viewCtrl切換的動畫的地方

UITransitionContextFromViewControllerKey

這個key可以抓出From的View

UITransitionContextToViewControllerKey

這個key可以抓出to的View

ex: viewCtrlA 要 present viewCtrlB

FromView 就是 viewCtrlA

toView 就是 viewCtrlB

再來就是寫動畫的時間啦~~~~

然後最重要的一點就是

[transitionContext completeTransition:![transitionContext transitionWasCancelled]];

這行!

一定要在動畫完以後才執行,否則可能會影響到你的動畫美觀程度



例如我自己是在 if (self.appearing) 這裡面執行不同的動畫

  1.         toView.frame = initialFrame;
  2.         toView.center = CGPointMake( [UIScreen mainScreen].bounds.size.width / 2 ,[UIScreen mainScreen].bounds.size.height / 2);
  3.         [containerView addSubview:toView];
  4.         
  5.         // Animate the view onscreen
  6.         
  7.         CALayer *viewLayer = toView.layer;
  8.         CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
  9.         animation.delegate = self;
  10.         animation.duration = duration;
  11.         animation.values = [NSArray arrayWithObjects:
  12.                             [NSNumber numberWithFloat:0.1],
  13.                             [NSNumber numberWithFloat:1.1],
  14.                             [NSNumber numberWithFloat:.9],
  15.                             [NSNumber numberWithFloat:1],
  16.                             nil];
  17.         animation.keyTimes = [NSArray arrayWithObjects:
  18.                               [NSNumber numberWithFloat:0.0],
  19.                               [NSNumber numberWithFloat:0.6],
  20.                               [NSNumber numberWithFloat:0.8],
  21.                               [NSNumber numberWithFloat:1.0],
  22.                               nil];
  23.         [viewLayer addAnimation:animation forKey:@"transform.scale"];

我是執行像這樣從中間跳出視窗放大彈跳的感覺

如果我把 “ [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; ”這行在24行地方執行的話

會在彈跳放大前 FromView 就被覆蓋拿掉了,彈跳前背景就變成白色

所以這時候就要

animation.delegate = self;

這行讓我去觸發動畫結束的函示

  1. - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
  2. {
  3.         
  4.     [self.transitionContext completeTransition:![self.transitionContext transitionWasCancelled]];

  5. }

我這裡才去執行

這樣彈跳出來的動畫就完美啦~~~


當然這樣你也只是完成這兩個 .h 跟 .m 檔而已

還要去使用囉

//***********************************************************************//


下面就來說如何去呼叫

在你要使用到 presnet 的那個 ViewCtrlA 當然要去 import 他,ViewCtrlB 也要import

所以要添加
  1. #import "customPresentAnimation.h"
這行

-------------------------------------------------------------------------------------------------------------------

接著要使用到 UIViewControllerTransitioningDelegate 這個

所以要添加

  1. @interface ViewCtrl ()<UIViewControllerTransitioningDelegate>
在這裡添加

ViewCtrlA 跟 ViewCtrlB 都要添加

-------------------------------------------------------------------------------------------------------------------

然後這行看你要在哪裡添加 

  1. viewCtrlB.transitioningDelegate = self;

則 viewCtrlB 裡添加

  1. self.transitioningDelegate = self;

我自己是在  - (void)viewDidLoad  裡分配空間時就順便設置了

-------------------------------------------------------------------------------------------------------------------

viewCtrlA 裡要添加

  1. - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
  2.                                                                   presentingController:(UIViewController *)presenting
  3.                                                                       sourceController:(UIViewController *)source
  4. {
  5.     customPresentAnimation *animator = [[customPresentAnimation alloc]init];
  6.     animator.appearing = YES;
  7.     return animator;
  8. }


viewCtrlB 裡要添加

  1. - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
  2.     
  3.     customPresentAnimation *animator = [[customPresentAnimation alloc]init];
  4.     animator.appearing = NO;
  5.     return animator;
  6.     
  7. }


-------------------------------------------------------------------------------------------------------------------

接著在你要 Present 的地方如往常的呼叫
  1.     [self.navigationController presentViewController:viewCtrlB animated:YES completion:NULL];


要 Dismissing 的地方呼叫
  1. [self dismissViewControllerAnimated:YES completion:NULL];

這樣子就大功告成啦!!!


網址裡還有其他的應用,有空大家也可以再去研究一下,改天比較閒時,我也會回去看的!!


沒有留言:

張貼留言