一、場景介紹
現在大多數APP 都有一個需求,就是隱藏某一個頁面的NavigationBar。很多開發者直接 [self.navigationController setNavigationBarHidden:YES] 就萬事大吉了。但是如果開發者試着將邊緣側滑返回功能加上之后,細心的同學就會發現,如果我們在一個隱藏NavigationBar的頁面和一個顯示NavigationBar 的頁面通過手勢來回切換后,再繼續push到更深層的頁面,頂部的NavigationBar就會出現錯亂的情況。因此好多APP 將側滑返回直接去掉了,但是這樣的話,整個APP 就會顯得比較粗糙,體驗不佳。其實早已iOS自帶了側滑返回功能,只不過大多數時候由於我們自定義了NavigationBar的leftBarButtonItem,導致了轉場交互代理interactivePopGestureRecognizer失效了,需要我們重新指定一下代理,具體實現方式,網上其他博客有很詳細的解釋,在此不再贅述。
二、問題解決
為了更好的描述問題,我特地定義了兩個控制器:1、Viewcontroller1(隱藏了導航欄,導航控制器的根控制器) ;2、Viewcontroller2(需要顯示導航控制器,由Viewcontroller1 push 而來)。 那么怎么解決“側滑返回功能”與“隱藏NavigationBar”共存呢?我的思路是:既然再同一個導航控制器棧內會出問題,那么我將Viewcontroller1和Viewcontroller2放在兩個導航控制器里不就可以了嗎?但是我們還想需要push的效果怎么辦?導航控制器是不允許在push一個新的導航控制器的。只能present出來一個新的導航控制器。因此我想到了自定義一個轉場動畫,讓present出來的效果如同push的效果一樣。具體的代碼等會會貼出,但是建議大家看一下王巍所寫的轉場動畫這部分知識。
1)設置側滑返回
#import <UIKit/UIKit.h> @protocol ModalViewControllerDelegate <NSObject> -(void) modalViewControllerDidClickedDismissButton:(UIViewController *)viewController; @end @interface BaseNavigationController : UINavigationController<UINavigationControllerDelegate,UIGestureRecognizerDelegate> //@property(nonatomic,weak)UIPanGestureRecognizer *popPan; //轉場代理 @property (nonatomic, weak) id<ModalViewControllerDelegate> transDelegate; @end
#import "BaseNavigationController.h"
@interface BaseNavigationController () @property(nonatomic,weak) UIViewController* currentShowVC; @end @implementation BaseNavigationController - (void)viewDidLoad { [super viewDidLoad]; //請忽略這段設置UI樣式的代碼,沒什么卵用 [self.navigationBar setBarTintColor:[Theme colorOne]]; [self.navigationBar setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys: [UIColor whiteColor],NSForegroundColorAttributeName, nil]]; [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent animated:YES]; [self.navigationBar setTranslucent:NO];
self.view.layer.shadowColor = [UIColor blackColor].CGColor; self.view.layer.shadowOffset = CGSizeMake(-2, 0); self.view.layer.shadowOpacity = 0.3; } //導航控制器一被創建則設置交互手勢代理 -(id)initWithRootViewController:(UIViewController *)rootViewController { BaseNavigationController* nvc = [super initWithRootViewController:rootViewController]; self.interactivePopGestureRecognizer.delegate = self; nvc.delegate = self; return nvc; }
//這段主要是判斷為了幫助手勢判斷當前響應手勢的控制器是不是根視圖控制器 -(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated { if (navigationController.viewControllers.count == 1) self.currentShowVC = Nil; else if(animated){ self.currentShowVC = viewController; }else{ self.currentShowVC = Nil; } }
//交互手勢代理(告訴手勢什么時候需要響應,什么時候不響應) -(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer == self.interactivePopGestureRecognizer) { return (self.currentShowVC == self.topViewController); } return YES; }
設置側滑返回我們需要在base類里重新設置代理,並實現轉場手勢的代理,告訴手勢當頁面為根視圖控制器時候手勢不響應,其實以上代碼就是實現這么點的功能,僅此而已。
2)設置轉場動畫
轉場動畫我是直接修改了王巍的代碼,原文地址在這里 https://onevcat.com/2013/10/vc-transition-in-ios7/ 。以下是我修改后的代碼,只是修改了動畫部分,使用方法是一致的。
#import "BouncePresentAnimation.h" @implementation BouncePresentAnimation - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return 0.25f; } - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { // 1. Get controllers from transition context UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; // 2. Set init frame for toVC CGRect screenBounds = [[UIScreen mainScreen] bounds]; CGRect finalFrame = [transitionContext finalFrameForViewController:toVC]; toVC.view.frame = CGRectOffset(finalFrame, screenBounds.size.width, 0); // 3. Add toVC's view to containerView UIView *containerView = [transitionContext containerView]; [containerView addSubview:toVC.view]; // 4. Do animate now NSTimeInterval duration = [self transitionDuration:transitionContext]; [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ toVC.view.frame = finalFrame; } completion:^(BOOL finished) { [transitionContext completeTransition:YES]; }]; } #import "NormalDismissAnimation.h" @implementation NormalDismissAnimation - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return 0.25f; } - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { // 1. Get controllers from transition context UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; // 2. Set init frame for fromVC CGRect screenBounds = [[UIScreen mainScreen] bounds]; CGRect initFrame = [transitionContext initialFrameForViewController:fromVC]; CGRect finalFrame = CGRectOffset(initFrame, screenBounds.size.width,0 ); // 3. Add target view to the container, and move it to back. UIView *containerView = [transitionContext containerView]; [containerView addSubview:toVC.view]; [containerView sendSubviewToBack:toVC.view]; // 4. Do animate now NSTimeInterval duration = [self transitionDuration:transitionContext]; [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ fromVC.view.frame = finalFrame; } completion:^(BOOL finished) { [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; }
3)又一個坑
大家將轉場動畫和側滑功能加入后會發現,我擦當我在ViewController2中再繼續push時,如果側滑則直接返回到了我們的ViewController1控制器,而不是我們想要的次棧頂控制器。這涉及到了手勢之間競爭的問題,也就是說轉場的手勢取代我們邊緣側滑手勢去響應了,因此我們要告訴系統,如果當前手勢所在的控制器不是present出來的導航控制器的根控制器的話,轉場手勢就不需要響應。因此需要修改王大師的一部分代碼
#import "SwipeUpInteractiveTransition.h" @interface SwipeUpInteractiveTransition()<UIGestureRecognizerDelegate> @property (nonatomic, assign) BOOL shouldComplete; @property (nonatomic, strong) UIViewController *presentingVC; @end @implementation SwipeUpInteractiveTransition -(void)wireToViewController:(UIViewController *)viewController { self.presentingVC = viewController; [self prepareGestureRecognizerInView:viewController.view]; } - (void)prepareGestureRecognizerInView:(UIView*)view { UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)]; gesture.delegate = self; [view addGestureRecognizer:gesture]; } -(CGFloat)completionSpeed { return 1 - self.percentComplete; } - (void)handleGesture:(UIPanGestureRecognizer *)gestureRecognizer { CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view.superview]; switch (gestureRecognizer.state) { case UIGestureRecognizerStateBegan: // 1. Mark the interacting flag. Used when supplying it in delegate. self.interacting = YES; [self.presentingVC dismissViewControllerAnimated:YES completion:nil]; break; case UIGestureRecognizerStateChanged: { // 2. Calculate the percentage of guesture CGSize screenSize = [UIScreen mainScreen].bounds.size; CGFloat fraction = translation.x / screenSize.width; //Limit it between 0 and 1 fraction = fminf(fmaxf(fraction, 0.0), 1.0); self.shouldComplete = (fraction > 0.5); [self updateInteractiveTransition:fraction]; break; } case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateCancelled: { // 3. Gesture over. Check if the transition should happen or not self.interacting = NO; if (!self.shouldComplete || gestureRecognizer.state == UIGestureRecognizerStateCancelled) { [self cancelInteractiveTransition]; } else { [self finishInteractiveTransition]; } break; } default: break; } } //如果不是根視圖控制器則不響應 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{ if ([self.presentingVC isKindOfClass:[BaseNavigationController class]]) { BaseNavigationController *nav = (BaseNavigationController*)self.presentingVC; if (nav.viewControllers.count >=2) { return NO; } } return YES; }
至此側滑和導航欄的隱藏則完美兼容,希望這篇文章對你有用~