iOS7新特性 ViewController轉場切換(二) 系統檢視表控制器容器的切換動畫---push pop present dismis,ios7viewcontroller
@上一章,介紹了主要的iOS7所增加的API,可以發現,它們不是一個個死的方法,蘋果給我們開發人員提供的是都是協議介面,所以我們能夠很好的單獨提出來寫成一個個類,在裡面實現我們各種自訂效果.
1.先來看看實現UIViewControllerAnimatedTransitioning的自訂動畫類
/** * 自訂的動畫類 * 實現協議------>@protocol UIViewControllerAnimatedTransitioning * 這個介面負責切換的具體內容,也即“切換中應該發生什麼” */@interface MTHCustomAnimator : NSObject <UIViewControllerAnimatedTransitioning>@end@implementation MTHCustomAnimator// 系統給出一個切換上下文,我們根據上下文環境返回這個切換所需要的花費時間- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{ return 1.0;}// 完成容器轉場動畫的主要方法,我們對於切換時的UIView的設定和動畫都在這個方法中完成- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{ // 可以看做為destination ViewController UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; // 可以看做為source ViewController UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; // 添加toView到容器上 [[transitionContext containerView] addSubview:toViewController.view]; toViewController.view.alpha = 0.0; [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ // 動畫效果有很多,這裡就展示個左位移 fromViewController.view.transform = CGAffineTransformMakeTranslation(-320, 0); toViewController.view.alpha = 1.0; } completion:^(BOOL finished) { fromViewController.view.transform = CGAffineTransformIdentity; // 聲明過渡結束-->記住,一定別忘了在過渡結束時調用 completeTransition: 這個方法 [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }];}
PS:從協議中兩個方法可以看出,上面兩個必須實現的方法需要一個轉場上下文參數,這是一個遵從UIViewControllerContextTransitioning 協議的對象。通常情況下,當我們使用系統的類時,系統架構為我們提供的轉場代理(Transitioning Delegates),為我們建立了轉場內容物件,並把它傳遞給動畫控制器。
// MainViewController@interface MTHMainViewController () <UINavigationControllerDelegate,UIViewControllerTransitioningDelegate>@property (nonatomic,strong) MTHCustomAnimator *customAnimator;@property (nonatomic,strong) PDTransitionAnimator *minToMaxAnimator;@property (nonatomic,strong) MTHNextViewController *nextVC;// 互動控制器 (Interaction Controllers) 通過遵從 UIViewControllerInteractiveTransitioning 協議來控制可互動轉場。@property (strong, nonatomic) UIPercentDrivenInteractiveTransition* interactionController;@end@implementation MTHMainViewController- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{ self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization } return self;}- (void)viewDidLoad{ [super viewDidLoad]; // Do any additional setup after loading the view. self.navigationItem.title = @"Demo"; self.view.backgroundColor = [UIColor yellowColor]; // 設定代理 self.navigationController.delegate = self; // 設定轉場動畫 self.customAnimator = [[MTHCustomAnimator alloc] init]; self.minToMaxAnimator = [PDTransitionAnimator new]; self.nextVC = [[MTHNextViewController alloc] init]; // Present的代理和自訂設定 _nextVC.transitioningDelegate = self; _nextVC.modalPresentationStyle = UIModalPresentationCustom; // 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]; // Present UIButton *modalButton = [UIButton buttonWithType:UIButtonTypeSystem]; modalButton.frame = CGRectMake(265, 500, 50, 50); [modalButton setTitle:@"Modal" forState:UIControlStateNormal]; [modalButton addTarget:self action:@selector(modal) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:modalButton]; // 實現互動操作的手勢 UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didClickPanGestureRecognizer:)]; [self.navigationController.view addGestureRecognizer:panRecognizer];}- (void)push{ [self.navigationController pushViewController:_nextVC animated:YES];}- (void)modal{ [self presentViewController:_nextVC animated:YES completion:nil];}#pragma mark - UINavigationControllerDelegate iOS7新增的2個方法// 動畫特效- (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 self.customAnimator; }else{ return nil; }}// 互動- (id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController*)navigationController interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>)animationController{ /** * 在非互動式動畫效果中,該方法返回 nil * 互動式轉場,自我理解意思是,使用者能通過自己的動作來(常見:手勢)控制,不同於系統預設給定的push或者pop(非互動式) */ return _interactionController;}#pragma mark - Transitioning Delegate (Modal)// 前2個用於動畫-(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{ self.minToMaxAnimator.animationType = AnimationTypePresent; return _minToMaxAnimator;}-(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{ self.minToMaxAnimator.animationType = AnimationTypeDismiss; return _minToMaxAnimator;}// 後2個用於互動- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator{ return _interactionController;}- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator{ return nil;}
@以上實現的是非互動的轉場,指的是完全按照系統指定的切換機制,使用者無法中途取消或者控制進度切換.那怎麼來實現互動轉場呢:
UIPercentDrivenInteractiveTransition實現了UIViewControllerInteractiveTransitioning介面的類,,可以用一個百分比來控制互動式切換的過程。我們在手勢識別中只需要告訴這個類的執行個體當前的狀態百分比如何,系統便根據這個百分比和我們之前設定的遷移方式為我們計算當前應該的UI渲染,十分方便。具體的幾個重要方法:
-(void)updateInteractiveTransition:(CGFloat)percentComplete 更新百分比,一般通過手勢識別的長度之類的來計算一個值,然後進行更新。之後的例子裡會看到詳細的用法
-(void)cancelInteractiveTransition 報告互動取消,返回切換前的狀態
–(void)finishInteractiveTransition 報告互動完成,更新到切換後的狀態
#pragma mark - 手勢互動的主要實現--->UIPercentDrivenInteractiveTransition- (void)didClickPanGestureRecognizer:(UIPanGestureRecognizer*)recognizer{ UIView* view = self.view; if (recognizer.state == UIGestureRecognizerStateBegan) { // 擷取手勢的觸摸點座標 CGPoint location = [recognizer locationInView:view]; // 判斷,使用者從右半邊滑動的時候,推出下一個VC(根據實際需要是推進還是推出) if (location.x > CGRectGetMidX(view.bounds) && self.navigationController.viewControllers.count == 1){ self.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init]; // [self presentViewController:_nextVC animated:YES completion:nil]; } } else if (recognizer.state == UIGestureRecognizerStateChanged) { // 擷取手勢在視圖上位移的座標 CGPoint translation = [recognizer translationInView:view]; // 根據手指拖動的距離計算一個百分比,切換的動畫效果也隨著這個百分比來走 CGFloat distance = fabs(translation.x / CGRectGetWidth(view.bounds)); // 互動控制器控制動畫的進度 [self.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) { [self.interactionController finishInteractiveTransition]; } else { [self.interactionController cancelInteractiveTransition]; } // 結束後一定要置為nil self.interactionController = nil; }}
@最後,給大家分享一個動畫特效:類似于飛兔雲傳的發送ViewController切換
@implementation PDTransitionAnimator#define Switch_Time 1.2- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return Switch_Time;}#define Button_Width 50.f#define Button_Space 10.f- (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 == AnimationTypeDismiss) { // 這個方法能夠高效的將當前顯示的view截取成一個新的view.你可以用這個截取的view用來顯示.例如,也許你只想用一張來做動畫,畢竟用原始的view做動畫代價太高.因為是截取了已經存在的內容,這個方法只能反應出這個被截取的view當前的狀態資訊,而不能反應這個被截取的view以後要顯示的資訊.然而,不管怎麼樣,調用這個方法都會比將view做成來載入效率更高. UIView * snap = [toView snapshotViewAfterScreenUpdates:YES]; [transitionContext.containerView addSubview:snap]; [snap setFrame:CGRectMake([UIScreen mainScreen].bounds.size.width - Button_Width - Button_Space, [UIScreen mainScreen].bounds.size.height - Button_Width - Button_Space, Button_Width, Button_Width)]; [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ [snap setFrame:[UIScreen mainScreen].bounds]; } completion:^(BOOL finished) { [UIView animateWithDuration:0.5 animations:^{ [[transitionContext containerView] addSubview:toView]; snap.alpha = 0; } completion:^(BOOL finished) { [snap removeFromSuperview]; [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; }]; } else { UIView * snap2 = [toView snapshotViewAfterScreenUpdates:YES]; [transitionContext.containerView addSubview:snap2]; UIView * snap = [fromView snapshotViewAfterScreenUpdates:YES]; [transitionContext.containerView addSubview:snap]; [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ [snap setFrame:CGRectMake([UIScreen mainScreen].bounds.size.width - Button_Width - Button_Space+ (Button_Width/2), [UIScreen mainScreen].bounds.size.height - Button_Width - Button_Space + (Button_Width/2), 0, 0)]; } completion:^(BOOL finished) { [UIView animateWithDuration:0.5 animations:^{ //snap.alpha = 0; } completion:^(BOOL finished) { [snap removeFromSuperview]; [snap2 removeFromSuperview]; [[transitionContext containerView] addSubview:toView]; // 切記不要忘記了噢 [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; }]; }}
@其中,snapshotViewAfterScreenUpdates 方法的解釋,我也不是很懂,反正初級來說會用就行,還可以參照下面的解析:
在iOS7 以前, 擷取一個UIView的快照有以下步驟: 首先建立一個UIGraphics的映像上下文,然後將視圖的layer渲染到該上下文中,從而取得一個映像,最後關閉映像上下文,並將映像顯示在UIImageView中。現在我們只需要一行代碼就可以完成上述步驟了:
[view snapshotViewAfterScreenUpdates:NO];
這個方法製作了一個UIView的副本,如果我們希望視圖在執行動畫之前儲存現在的外觀,以備之後使用(動畫中視圖可能會被子視圖遮蓋或者發生其他一些變化),該方法就特別方便。
afterUpdates參數表示是否在所有效果應用在視圖上了以後再擷取快照。例如,如果該參數為NO,則立馬擷取該視圖現在狀態的快照,反之,以下代碼只能得到一個空白快照:
[view snapshotViewAfterScreenUpdates:YES];
[view setAlpha:0.0];
由於我們設定afterUpdates參數為YES,而視圖的透明度值被設定成了0,所以方法將在該設定應用在視圖上了之後才進行快照,於是乎螢幕空空如也。另外就是……你可以對快照再進行快照……繼續快照……
最後,主要代碼已經給出,我已經上傳完整代碼了,大家有意向的可以去下載下來看看(唉,主要是CSDN不支援gif動態圖,好蛋疼,朋友在部落格園的部落格都支援,但是我又不喜歡部落格園的風格,看來以後自己弄個網域名稱部落格是王道,後續我也會把我的一些代碼分享到github上)
@轉載請註明,