上一篇記錄了利用系統私有變數和方法實現右滑返回手勢功能:http://www.cnblogs.com/ALongWay/p/5893515.html 這篇繼續記錄另一種方案:利用UINavigationController的delegate方法。 核心代理方法有如下兩個: 第一個代理方法,要求在視圖 ...
上一篇記錄了利用系統私有變數和方法實現右滑返回手勢功能:http://www.cnblogs.com/ALongWay/p/5893515.html
這篇繼續記錄另一種方案:利用UINavigationController的delegate方法。
核心代理方法有如下兩個:
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS(7_0); - (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
第一個代理方法,要求在視圖控制器變換時候,返回一個實現UIViewControllerInteractiveTransitioning協議的對象,該對象用於控制交互進度和狀態。
UIKit.h/UIViewControllerTransitioning.h中已經準備好了一個這樣的對象,名為UIPercentDrivenInteractiveTransition。
主要關註其如下三個方法:
- (void)updateInteractiveTransition:(CGFloat)percentComplete;//控制交互進度的百分比
- (void)cancelInteractiveTransition;//中途取消交互
- (void)finishInteractiveTransition;//完成交互
可以在一個實現了代理方法的類中,定義一個手勢目標方法來調用上述方法,達到控製作為變數的UIPercentDrivenInteractiveTransition對象的目的。
主要代碼如下:
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController { if ([animationController isKindOfClass:[PopAnimation class]]) return _interactivePopTransition; return nil; } - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { if (operation == UINavigationControllerOperationPop) return _popAnimation; return nil; } - (void)handlePopGestureOperation:(UIPanGestureRecognizer *)recognizer{ //將手指在屏幕上的移動距離與屏幕寬度比例作為動畫的進度 CGPoint translationInScreen = [recognizer translationInView:[UIApplication sharedApplication].keyWindow]; CGFloat progress = translationInScreen.x / [UIScreen mainScreen].bounds.size.width; progress = MIN(1.0, MAX(0.0, progress)); if (recognizer.state == UIGestureRecognizerStateBegan) { //新建手勢交互對象 _interactivePopTransition = [[UIPercentDrivenInteractiveTransition alloc] init]; //開始執行pop動畫 [_naviController popViewControllerAnimated:YES]; }else if (recognizer.state == UIGestureRecognizerStateChanged) { //更新進度 [_interactivePopTransition updateInteractiveTransition:progress]; }else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled) { //手勢結束時如果進度大於一半,那麼就完成pop操作,否則取消 if (progress > 0.5) { [_interactivePopTransition finishInteractiveTransition]; }else { [_interactivePopTransition cancelInteractiveTransition]; } //手勢交互結束,清理對象 _interactivePopTransition = nil; } }
第二個代理方法,要求在操作開始時候,返回一個實現UIViewControllerAnimatedTransitioning協議的對象,該對象實現了動畫內容和執行時間。
該協議有三個方法:
// This is used for percent driven interactive transitions, as well as for container controllers that have companion animations that might need to // synchronize with the main animation. - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext; // This method can only be a nop if the transition is interactive and not a percentDriven interactive transition. - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext; @optional // This is a convenience and if implemented will be invoked by the system when the transition context's completeTransition: method is invoked. - (void)animationEnded:(BOOL) transitionCompleted;
第一個方法返回動畫時間;第二個方法,可以得到實現了UIViewControllerContextTransitioning協議的對象;第三個方法可選,如果transitionContext對象調用了completeTransition:方法,該代理方法會被調用。
重點是第二個方法的transitionContext對象,其重要的方法如下:
// The view in which the animated transition should take place. - (nullable UIView *)containerView;//動畫所在的容器view - (BOOL)transitionWasCancelled;//轉換是否取消// This must be called whenever a transition completes (or is cancelled.) // Typically this is called by the object conforming to the // UIViewControllerAnimatedTransitioning protocol that was vended by the transitioning // delegate. For purely interactive transitions it should be called by the // interaction controller. This method effectively updates internal view // controller state at the end of the transition. - (void)completeTransition:(BOOL)didComplete;//完成轉換,必須在動畫完成時調用 // Currently only two keys are defined by the // system - UITransitionContextToViewControllerKey, and // UITransitionContextFromViewControllerKey. // Animators should not directly manipulate a view controller's views and should // use viewForKey: to get views instead. - (nullable __kindof UIViewController *)viewControllerForKey:(NSString *)key;//取得視圖控制器 // Currently only two keys are defined by the system - // UITransitionContextFromViewKey, and UITransitionContextToViewKey // viewForKey: may return nil which would indicate that the animator should not // manipulate the associated view controller's view. - (nullable __kindof UIView *)viewForKey:(NSString *)key NS_AVAILABLE_IOS(8_0);//取得視圖控制器的view,相容iOS7,可以直接用上面取到的viewController.view
創建一個實現UIViewControllerAnimatedTransitioning協議的對象PopAnimation,重要代碼如下:
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return _transitionDuration; } - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { _transitionContext = transitionContext; //當前控制器 UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; //動畫結束顯示的控制器 UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; //執行交互動畫的容器view UIView *containerView = [transitionContext containerView]; [containerView insertSubview:toViewController.view belowSubview:fromViewController.view]; NSTimeInterval duration = [self transitionDuration:transitionContext]; switch (_popAnimationType) { case PopAnimationTranslation: { CGFloat containerWidth = containerView.frame.size.width; toViewController.view.transform = CGAffineTransformMakeTranslation(-containerWidth / 4.0, 0); [UIView animateWithDuration:duration animations:^{ toViewController.view.transform = CGAffineTransformMakeTranslation(0, 0); fromViewController.view.transform = CGAffineTransformMakeTranslation(containerWidth, 0); }completion:^(BOOL finished) { //動畫結束,必須調用此方法 [transitionContext completeTransition:!transitionContext.transitionWasCancelled]; }]; break; } case PopAnimationFlip: { [UIView beginAnimations:@"View Flip" context:nil]; [UIView setAnimationDuration:duration]; [UIView setAnimationDelegate:self]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:containerView cache:YES]; [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:)]; [UIView commitAnimations]; [containerView exchangeSubviewAtIndex:0 withSubviewAtIndex:1]; break; } case PopAnimationCube: { CATransition *transiton = [CATransition animation]; transiton.type = @"cube"; transiton.subtype = @"fromLeft"; transiton.duration = duration; transiton.removedOnCompletion = NO; transiton.fillMode = kCAFillModeForwards; transiton.delegate = self; [containerView.layer addAnimation:transiton forKey:nil]; [containerView exchangeSubviewAtIndex:0 withSubviewAtIndex:1]; break; } } } - (void)animationDidStop:(CATransition *)anim finished:(BOOL)flag { [_transitionContext completeTransition:!_transitionContext.transitionWasCancelled]; }
剩下的工作:
在上一篇的基礎上,修改UINavigationController的base_pushViewController方法
- (void)base_pushViewController:(UIViewController *)viewController animated:(BOOL)animated { if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.base_panGestureRecognizer]) { [self.interactivePopGestureRecognizer.view addGestureRecognizer:self.base_panGestureRecognizer]; self.base_panGestureRecognizer.delegate = [self base_panGestureRecognizerDelegateObject]; self.base_currentDelegateObject = [[NavigationControllerDelegateObject alloc] initWithUINavigationController:self]; [self.base_panGestureRecognizer addTarget:self.base_currentDelegateObject action:@selector(handlePopGestureOperation:)]; self.interactivePopGestureRecognizer.enabled = NO; } [self base_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController]; if (![self.viewControllers containsObject:viewController]) { [self base_pushViewController:viewController animated:animated]; } }
可以看到,去除了私有變數和方法的調用,自定義了名為NavigationControllerDelegateObject的對象,其實現了UINavigationControllerDelegate協議,並實現了pan手勢的處理方法。在此,我暴露了該屬性對象,考慮到以後可能會增加或者修改UINavigationControllerDelegate的實現協議,可以為該對象增加分類來達到目的;還有一個目的,便於修改popAnimation的動畫類型和執行時間。
總結:
兩篇記錄,從不同的方向,記錄了右滑返回手勢功能的實現。利用系統變數和方法,方便快捷;利用協議方法,高度自定義。
但是,利用協議方法,需要處理的方面比較多。雖然上述已經記錄了實現核心,示例代碼中也有詳細代碼,但是仍然還有很多方面未考慮。
目前,我發現的一些問題:
1.導航欄交互效果也需要自行實現,暫未實現原生效果
2.導航欄由隱藏到顯示的效果,有時會出現異常
3.如果帶有bottomBar,效果不理想
如果有好的解決辦法,希望告知,謝謝。
base項目已更新:[email protected]:ALongWay/base.git
項目增加了新的UINavigationController+PopOperation分類,但是未引用到項目中,需要的同學,可先刪除PopGesture分類的引用,再添加該分類的應用。