【轉】自定義presentviewcontroller和pushviewcontroller轉場動畫
自定義NavigationController動畫
首先,實現一個非常簡單的UINavigationController轉場,一般會這麼幹
實現FirstViewController,加到Window上(沒用storyboard和xib)
實現FirstViewController上面有個按鈕,點選後push到SecondViewController
貼一下FirstViewController的關鍵程式碼,這很簡單
- (void)viewDidLoad { [super viewDidLoad]; //init First self.navigationItem.title = @"First"; self.view.backgroundColor = [UIColor orangeColor]; //init Second secondViewController = [[SecondViewController alloc] init]; // Push UIButton *pushButton = [UIButton buttonWithType:UIButtonTypeSystem]; pushButton.frame = CGRectMake(140, 200, 40, 40); [pushButton setTitle:@"Push" forState:UIControlStateNormal]; [pushButton addTarget:self action:@selector(push) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:pushButton]; } - (void)push { [self.navigationController pushViewController:secondViewController animated:YES]; }
下面我們要自定義這個push動畫
我們先實現一個push自定義動畫類姑且叫做CustomPushAnimation,它實現UIViewControllerAnimatedTransitioning協議,用來定義一個非互動動畫(就是動畫過程中沒互動)
裡面也用到了UIViewControllerContextTransitioning這個協議,可以理解為轉場動畫的上下文,一個容器。下面是程式碼.h和.m,記住這是一個自定義的push的動畫
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface CustomPushAnimation : NSObject<UIViewControllerAnimatedTransitioning> @end
#import "CustomPushAnimation.h" @implementation CustomPushAnimation - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext { return 3.0; } - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { //目的ViewController UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; //起始ViewController UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; //新增toView到上下文 [[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view]; //自定義動畫 toViewController.view.transform = CGAffineTransformMakeTranslation(320, 568); [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ fromViewController.view.transform = CGAffineTransformMakeTranslation(-320, -568); toViewController.view.transform = CGAffineTransformIdentity; } completion:^(BOOL finished) { fromViewController.view.transform = CGAffineTransformIdentity; // 宣告過渡結束時呼叫 completeTransition: 這個方法 [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; } @end
下面在FirstViewController裡面初始化CustomPush動畫,並給FirstViewController新增UINavigationController代理
//FirstViewController viewDidLoad
self.navigationController.delegate = self;
//init CustomPush
customPush = [[CustomPushAnimation alloc] init];
為什麼要新增代理呢?怎麼用這個CustomPush動畫呢?
答案就是我們要用UINavigationControllerDelegate的
– (id<UIViewControllerAnimatedTransitioning>) navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
這個方法來選中Push轉場的時候用我們自己定義的CustomPush動畫。
#pragma mark - UINavigationControllerDelegate iOS7<span class="s1">非互動自定義</span>Navigation<span class="s1">轉場</span>
// 動畫特效
- (id<UIViewControllerAnimatedTransitioning>) navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
/**
* typedef NS_ENUM(NSInteger, UINavigationControllerOperation) {
* UINavigationControllerOperationNone,
* UINavigationControllerOperationPush,
* UINavigationControllerOperationPop,
* };
*/
//push的時候用我們自己定義的customPush
if (operation == UINavigationControllerOperationPush) {
return customPush;
}else{
return nil;
}
}
大功告成,現在就執行一下試試吧,可以發現動畫像我想的一樣,FirstViewController往左上角飄,SecondViewController緊隨其後,這樣完成了轉場,這樣我們就自定義了一個push的轉場動畫!
很有趣吧,下面照葫蘆畫瓢,做一個Pop動畫吧!聰明的你一定知道,只需要在自定義轉場的代理裡面加一個if語句判斷pop就好
// 動畫特效
- (id<UIViewControllerAnimatedTransitioning>) navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
/**
* typedef NS_ENUM(NSInteger, UINavigationControllerOperation) {
* UINavigationControllerOperationNone,
* UINavigationControllerOperationPush,
* UINavigationControllerOperationPop,
* };
*/
if (operation == UINavigationControllerOperationPush) {
return customPush;
}else if (operation == UINavigationControllerOperationPop) {
return customPop;
}else{
return nil;
}
}
**********************************************************************************************************************
自定義PresentViewController動畫
NavigationController搞定,Present也依葫蘆畫瓢
工程裡新增第三個ViewController 叫做ThirdViewController,然後在FirstViewController裡面新增下面程式碼
//init Third
thirdViewController = [[ThirdViewController alloc] init];
// Present
UIButton *presentButton = [UIButton buttonWithType:UIButtonTypeSystem];
presentButton.frame = CGRectMake(110, 400, 100, 40);
[presentButton setTitle:@"Present" forState:UIControlStateNormal];
[presentButton addTarget:self action:@selector(present) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:presentButton];
然後ThirdViewController新增一個按鈕用來Dismiss自己,這個就不寫了,然後執行起來實現的功能就很正常,點選Present按鈕就會自下而上的一個動畫推入新的ViewController,這很正常,然後現在來實現高階動畫
首先,和Navigation類似,在FirstViewController我們要做一些準備工作
修改FirstViewController的協議
@interface FirstViewController ()<UINavigationControllerDelegate,UIViewControllerTransitioningDelegate>
然後和NavigationController有區別的是給自己的“下家兒”,也就是要轉去的ViewController設定代理
thirdViewController.transitioningDelegate = self;
接下來就要在present時候和dismiss兩個協議方法設定自定義動畫,下面兩個方法來於UIViewControllerTransitioningDelegate
#pragma mark - UIViewControllerTransitioningDelegate
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
customPresent.animationType = AnimationTypePresent;
return customPresent;
}
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
customPresent.animationType = AnimationTypeDismiss;
return customPresent;
}
那麼,customPresent.animationType = AnimationTypePresent;這是什麼東西呢?不要著急,這個就是自定義的Present動畫效果,下面就講他的實現,但是目前已經可以看到程式碼寫起來和NavigationController的自定義動畫非常像,對仗工整,都是協議,設定代理,呼叫代理時候使用自定義的動畫。
下面定義真正的自定義動畫CustomPresentAnimation.h
typedef enum {
AnimationTypePresent,
AnimationTypeDismiss
} AnimationType;
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface CustomPresentAnimation : NSObject <UIViewControllerAnimatedTransitioning>
@property (nonatomic, assign) AnimationType animationType;
@end
#import "CustomPresentAnimation.h"
@implementation CustomPresentAnimation
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 1.3;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView * toView = toViewController.view;
UIView * fromView = fromViewController.view;
if (self.animationType == AnimationTypePresent) {
//snapshot方法是很高效的截圖
//First放下面
UIView * snap = [fromView snapshotViewAfterScreenUpdates:YES];
[transitionContext.containerView addSubview:snap];
//Third放上面
UIView * snap2 = [toView snapshotViewAfterScreenUpdates:YES];
[transitionContext.containerView addSubview:snap2];
snap2.transform = CGAffineTransformMakeTranslation(-320, 0);
//進行動畫
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{
snap2.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
//刪掉截圖
[snap removeFromSuperview];
[snap2 removeFromSuperview];
//新增檢視
[[transitionContext containerView] addSubview:toView];
//結束Transition
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
} else {
//First 放下面
UIView * snap = [toView snapshotViewAfterScreenUpdates:YES];
[transitionContext.containerView addSubview:snap];
//Third 放上面
UIView * snap2 = [fromView snapshotViewAfterScreenUpdates:YES];
[transitionContext.containerView addSubview:snap2];
//進行動畫
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{
snap2.transform = CGAffineTransformMakeTranslation(-320, 0);
} completion:^(BOOL finished) {
//刪掉截圖
[snap removeFromSuperview];
[snap2 removeFromSuperview];
//新增檢視
[[transitionContext containerView] addSubview:toView];
//結束Transition
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
}
@end
呼叫的時候就和上面程式碼所說一樣,在UIViewControllerTransitioningDelegate代理裡完成,那麼自定義的Present轉場就是這樣了。執行一下,會發現很調皮的ThirdView在Present轉場的時候從左面彈性的進入螢幕。
互動式的動畫
想想iOS7的UINavigationController自帶的動畫,可以用手指慢慢往左滑動來pop出一個頁面,動畫是隨著手指的移動互動式呈現的,這個就很有趣了,那麼除了系統幫我們實現的,我們自己也可以自定義互動式動畫了。
下面就來給我們的App新增一個Pan手勢,給之前實現的Present動畫增加一個互動式功能。
FirstViewController的viewdidload新增pan手勢
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didClickPanGestureRecognizer:)];
[self.navigationController.view addGestureRecognizer:panRecognizer];
然後執行這個方法,在手勢執行的時候重新整理互動狀態,核心方法是updateInteractiveTransition
#pragma mark - 手勢互動的主要實現--->UIPercentDrivenInteractiveTransition
- (void)didClickPanGestureRecognizer:(UIPanGestureRecognizer*)recognizer
{
UIView* view = self.view;
if (recognizer.state == UIGestureRecognizerStateBegan) {
interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];
[self presentViewController:thirdViewController animated:YES completion:nil];
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:view];
CGFloat distance = fabs(translation.x / CGRectGetWidth(view.bounds));
[interactionController updateInteractiveTransition:distance];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint translation = [recognizer translationInView:view];
CGFloat distance = fabs(translation.x / CGRectGetWidth(view.bounds));
if (distance > 0.5) {
[interactionController finishInteractiveTransition];
} else {
[interactionController cancelInteractiveTransition];
}
interactionController = nil;
}
}
這裡需要提一下,初始化下面的程式碼,用於互動式轉場,
//通過 UIViewControllerInteractiveTransitioning 協議進行互動轉場。
UIPercentDrivenInteractiveTransition* interactionController;
然後根據UIViewControllerTransitioningDelegate這個協議新增兩個方法,來說明我們使用互動式轉場
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator
{
return interactionController;
}
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator
{
return nil;
}