美文网首页
导航的相关问题

导航的相关问题

作者: 简_爱SimpleLove | 来源:发表于2017-04-14 19:33 被阅读145次

    导航本质

    导航条也是继承自UIViewController,它有自己的view,我们的view都是放在UIViewControllerWrapperView上面。所以如果我们要自定义一个导航条,可以继承自UIViewController,然后用一个数组管理我们的viewControllers,然后再把viewController的view加到我们定义的导航条的view上面。

    导航分为三个区:导航区(nav.navigationBar),内容区(nav.viewControllers),工具区(nav.toolbar)
    其中工具区默认被隐藏。

    导航内容展示的几种区别

    // 透明全局(默认)
    - (void)translucentAndAll{
        self.navigationController.navigationBar.translucent = YES;
        self.edgesForExtendedLayout = UIRectEdgeAll;
    //    self.automaticallyAdjustsScrollViewInsets = YES;
    //    self.extendedLayoutIncludesOpaqueBars = NO;
    }
    
    // 透明64
    - (void)translucentAnd64{
        self.navigationController.navigationBar.translucent = YES;
        self.edgesForExtendedLayout = UIRectEdgeNone;   // 不拓展它的区域,从导航栏下面开始
    }
    
    // 不透明64
    - (void)noTranslucentAnd64{
        self.navigationController.navigationBar.translucent = NO;
       // self.edgesForExtendedLayout = UIRectEdgeNone;
    }
    
    // 不透明全局
    - (void)noTranslucentAndAll{
        self.navigationController.navigationBar.translucent = NO;
        self.extendedLayoutIncludesOpaqueBars = YES; // 延伸区域包括不透明的bar
    }
    

    导航条返回按钮既有图片也有文字

    可以创建一个view,在view上面添加图片和文字,再调用self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:view];这句代码,但是系统默认的会和最左边有20的像素


    要解决这个20像素的问题,可以将view上添加的控件的x值设为负数,但是这样点击那小于20像素的部分就不会响应事件。如下:
        view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
        view.backgroundColor = [UIColor redColor];
        UIImageView *imageV = [[UIImageView alloc] initWithFrame:CGRectMake(-10, 0, 20, 20)];
        imageV.image = [UIImage imageNamed:@"arrow.png"];
        [view addSubview:imageV];
        self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:view];
    

    注意这里只能是将view上添加的控件的x值设为负数,将view的x值设为负数,距离最左边还是20像素,没有意义。

    所以最好的解决办法是将view直接添加到navigationBar上面[self.navigationController.navigationBar addSubview:view];。然后在viewWillDisappear的方法中从父控件中移除,才不会影响到下一个界面。

    导航条返回按钮只有图片

    这里需要将图片渲染,并且只有将navigationBar的backIndicatorImage和backIndicatorTransitionMaskImage两个图片属性都设置为它才会有效果,如下:

        UIImage *image = [UIImage imageNamed:@"arrow.png"];
        //一定要加这句代码,出来的才是图片的颜色,不然一直系统的蓝色
        image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
        self.navigationController.navigationBar.backIndicatorImage = image;
        self.navigationController.navigationBar.backIndicatorTransitionMaskImage = image;
    

    另外:self.navigationItem.backBarButtonItem设置的是当前界面用导航推过去的下一个界面的返回按钮。

    设置导航栏为透明

    self.navigationController.navigationBar.translucent属性必须为YES。
    方法一:给navigationBar添加一个透明的背景图片,但是设置它的navigationBar.backgroundColor背景色无效

    -(void)transluentStyle{
        [self.navigationController.navigationBar setBackgroundImage:self.image forBarMetrics:UIBarMetricsDefault];
        // 这个方法不行
    //    self.navigationController.navigationBar.backgroundColor = [UIColor clearColor];
    }
    - (UIImage *)image{
        
        UIGraphicsBeginImageContext(CGSizeMake(100, 100));
        [[[UIColor whiteColor] colorWithAlphaComponent:0] setFill];
        UIRectFill(CGRectMake(0, 0, 100, 100));
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return image;
    }
    

    方法二:遍历子控件,并将_UIBarBackground的控件背景设置为透明色

    - (void)transluentTwoStyle{
        
        NSArray *ary  = [self.navigationController.navigationBar subviews];
        UIColor *alphaColor = [[UIColor whiteColor] colorWithAlphaComponent:0];
        for (int i = 0; i < ary.count; i++) {
            UIView *view = ary[i];
            view.backgroundColor = alphaColor;
    
            for (int j = 0; j < view.subviews.count; j++) {
                UIView *subView = view.subviews[j];
                subView.backgroundColor = alphaColor;
                for (int k = 0; k < subView.subviews.count; k++) {
                    UIView *subsubView = subView.subviews[k];
                    subsubView.backgroundColor = alphaColor;
                }
            }
            if([view isKindOfClass:NSClassFromString(@"_UIBarBackground")]){
                
                view.backgroundColor = alphaColor;
    
            }
        }
        
    }
    

    去掉导航下面的那条黑线

    方法一:通过Debug View Hierarchy层级可以看到那根黑线的y坐标是64,在导航栏下面,所以我们只要如下就好
    self.navigationController.navigationBar.clipsToBounds = YES;
    方法二:老办法,递归遍历找到黑线并隐藏

    - (void)viewDidLoad {
        [super viewDidLoad];
        self.title = @"黑线处理";
        // 导航条背景色
        self.navigationController.navigationBar.barTintColor = [UIColor yellowColor];
        UIImageView *iamgeV = [self findBackLineImageV:self.navigationController.navigationBar];
        iamgeV.hidden = YES;
    }
    - (UIImageView *)findBackLineImageV:(UIView*)view{
        
        // 递归写法
        // 找到了黑线就返回
        if([view isKindOfClass:[UIImageView class]] && view.frame.size.height <= 1){
            return (UIImageView*)view;
        }
        NSArray *viewAry = view.subviews;
        // 如果不是黑线,就遍历它的子view,再走本身这个方法,找出黑线
        for (int i = 0; i < viewAry.count; i++) {
            UIView *tmpV = [self findBackLineImageV:viewAry[i]];
            if (tmpV) {
                return (UIImageView*)tmpV;
            }
        }
        return nil;
    }
    

    导航上面Item按钮的距离设置

    可以用系统的方法,在两个Item直接加一个空格Item(这种思维也可以用到其他不好控制的布局上面,当要改变两个直接的距离的时候,直接改变中间控件的宽高就可以了)

        NSMutableArray *barItems = [NSMutableArray array];
        UIBarButtonItem *barItem = [[UIBarButtonItem alloc] initWithTitle:@"Nav" style:UIBarButtonItemStylePlain target:self action:@selector(showNav)];
        UIBarButtonItem *barItemSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
        barItemSpace.width = 60;
        UIBarButtonItem *barItemT = [[UIBarButtonItem alloc] initWithTitle:@"view" style:UIBarButtonItemStylePlain target:self action:@selector(showNavTwo)];
        
        [barItems addObject:barItem];
        [barItems addObject:barItemSpace];
        [barItems addObject:barItemT];
        
        self.navigationItem.rightBarButtonItems = barItems;
    

    导航栏标题

    我们也可以自己写一个view,赋给导航条的titleView,如下:

        UIView *view = [[UIView alloc] initWithFrame:CGRectMake(20, 0, 50, 50)];
        view.backgroundColor = [UIColor redColor];
        self.navigationItem.titleView = view;
    

    其中这里设置view的x值为20也是没有意义的和leftBarButtonItem一样,它一样还是会居中显示

    重点:其实很多有些复杂,用系统不方便的一些设置,我们完全可以不用系统的,直接写一个控件添加到navigationBar上面,然后再在viewWillDisappear的方法中从父控件中移除,不影响到下一个界面就好。

    navigationBarHidden和navigationBar.hidden

        self.navigationController.navigationBarHidden = YES;
        self.navigationController.navigationBar.hidden = YES;
    

    第一个方法是navigationController的属性,第二个是navigationBar本身的属性。其中第一个可以看做是执行可以看作是remove操作navigationBar操作[self.navigationController.navigationBar removeFromSuperview];
    注意:两个隐藏的方法要分别对应使用,navigationBarHidden设置的为YES,当要显示的时候,也必须是将这个属性设置为NO,不能套用,不然会显示不出来。

    导航栏的隐藏还有一种方法,就是通过代理方法来实现,设置self为导航控制器的代理,实现代理方法,在将要显示控制器中设置导航栏隐藏和显示:

    @interface WLHomePageController () <UINavigationControllerDelegate>
    @end
    @implementation WLHomePageController 
    #pragma mark - lifeCycle
    - (void)viewDidLoad {
        [super viewDidLoad];
        // 设置导航控制器的代理为self
        self.navigationController.delegate = self;
    }
    #pragma mark - UINavigationControllerDelegate
    // 将要显示控制器
    - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
        // 判断要显示的控制器是否是自己
        BOOL isShowHomePage = [viewController isKindOfClass:[self class]];
        [self.navigationController setNavigationBarHidden:isShowHomePage animated:YES];
    }
    

    自己实现一个导航返回的动画效果

    分析:我们需要用到手势,获取手势的移动距离,从而来进行操作和判断。有点类似于只有两个页面的scrollview的滚动,其中转场的地方就是scrollview,前一个控制器的view和当前控制器的view就相当于scrollview的上面添加的两个view,只是还要包括导航条的操作,不过导航条不太好操作。具体的代码如下,里面有详细的注释:

    #import "EOCEOCAnimaTheoryVC.h"
    #import "UIView+EOCFrame.h"
    
    
    @interface EOCEOCAnimaTheoryVC (){
        
        UIView *transferView;
        UIView *preView;
        UINavigationBar *preNavBar;
    }
    
    @end
    
    @implementation EOCEOCAnimaTheoryVC
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.title = @"动画原理";
        UIPanGestureRecognizer *panGes = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleNavTransition:)];
        [self.view addGestureRecognizer:panGes];
        
    }
    - (void)viewDidAppear:(BOOL)animated{
        
        [super viewDidAppear:animated];
        
        transferView = [self.view superview];  // 它的父控件UIViewControllerWrapperView,就是导航条自己本身的view,其他的view变化都是通过添加在它本身上面的
        if (self.navigationController.viewControllers.count >= 2) {
            
            NSArray *viewCtrAry = self.navigationController.viewControllers;
            preView = ((UIViewController*)[viewCtrAry objectAtIndex:viewCtrAry.count -2]).view; //导航条中的上一个控制器的view
            
        }
    }
    
    - (void)handleNavTransition:(UIPanGestureRecognizer*)gesture{
        
        CGPoint gapPoint = [gesture translationInView:gesture.view]; // 获取手势的偏移量
        [gesture setTranslation:CGPointZero inView:gesture.view];  // 每次滑动完置为零,避免累加
        float width = gapPoint.x;
        //CGPoint posPoint = [gesture locationInView:gesture.view];
        
        if (gesture.state == UIGestureRecognizerStateBegan) { // 手势开始
            
            [preView setFrame:CGRectMake(-preView.eocW, preView.eocOrigin.y, preView.eocW, preView.eocH)]; // 将preView添加到self.view的紧挨着左边,但是这时是在屏幕外
            [transferView addSubview:preView];
            [transferView bringSubviewToFront:self.view];
            
    //        preNavBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(-[UIScreen mainScreen].bounds.size.width, 0, [UIScreen mainScreen].bounds.size.width, 64)];  // 设置前一个导航栏
    //        NSMutableArray *array = [NSMutableArray array];
    //        [array addObjectsFromArray:self.navigationController.navigationBar.items];  //获取navigationBar上所有的items
    //        [array removeLastObject];  // 移除掉当前navigationBar上的items,就是前一个navigationBar上面所有的items
    //        preNavBar.items = array;  // 将items赋给前一个navigationBar
    //        [transferView addSubview:preNavBar];  // 添加到转场view上面
            
        }else if(gesture.state == UIGestureRecognizerStateChanged) {  // 手势开始变化
            
            // 将preView和self.view在他们的父控件transferView上都向右移动手势移动的距离长度
            [preView setFrame:CGRectMake(preView.eocOrigin.x + width, preView.eocOrigin.y, preView.eocW, preView.eocH)];
            [self.view setFrame:CGRectMake(self.view.eocOrigin.x + width, self.view.eocOrigin.y, self.view.eocW, self.view.eocH)];
            
    //        // 移动当前导航栏
    //        self.navigationController.navigationBar.frame = ({
    //            CGRect rect = self.navigationController.navigationBar.frame;
    //            rect.origin.x = self.view.frame.origin.x + width;
    //            rect;
    //        });
    //        // 移动前一个导航栏
    //        preNavBar.frame = ({
    //            CGRect rect = preNavBar.frame;
    //            rect.origin.x = preNavBar.frame.origin.x + width;
    //            rect;
    //        });
        }else{
            
            if (self.view.eocOrigin.x > self.view.eocW/2) {  // 当向右滑动超过一半时,屏幕中间还是显示preView,滑过去了
                [UIView animateWithDuration:0.3 animations:^{
                    [preView setFrame:CGRectMake(0, preView.eocOrigin.y, preView.eocW, preView.eocH)];  //将前一个控制器view移动到屏幕上
                    [self.view setFrame:CGRectMake(self.view.eocW, self.view.eocOrigin.y, self.view.eocW, self.view.eocH)];  //将当前控制器view移动到屏幕外
                    
                } completion:^(BOOL finished) {
                    NSMutableArray *array = [NSMutableArray array];
                    [array addObjectsFromArray:self.navigationController.viewControllers];
                    [array removeLastObject];  //移除掉最后一个控制器,即当前控制器
                    self.navigationController.viewControllers = array;
                    [self.view removeFromSuperview];  //将最后一个控制器,即当前控制器的view从父控件中移除
                }];
            }else{  // 当向右滑动没有超过一半时,屏幕中间还是显示的self.view,没有滑过去
                [UIView animateWithDuration:0.3 animations:^{
                    [preView setFrame:CGRectMake(-preView.eocW, preView.eocOrigin.y, preView.eocW, preView.eocH)];
                    [self.view setFrame:CGRectMake(0, self.view.eocOrigin.y, self.view.eocW, self.view.eocH)];
                } completion:^(BOOL finished) {
                    [preView removeFromSuperview];  //将preView从父控件中移除
                }];
            }
        }
    }
    @end
    

    其中需要注意的是transferView转场的view就是导航条本身的view:


    效果如图:


    上面代码中注释的部分,是关于导航条的动画操作,但是会有许多问题,如果打开运行,会有下面的问题,还没深究。另外导航条也有自己的代理UINavigationBarDelegate和代理方法

    另外导航条也有自己的代理UINavigationBarDelegate和代理方法

    - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPushItem:(UINavigationItem *)item; // called to push. return NO not to.
    - (void)navigationBar:(UINavigationBar *)navigationBar didPushItem:(UINavigationItem *)item;    // called at end of animation of push or immediately if not animated
    - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;  // same as push methods
    - (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item;
    

    替换系统转场动画手势

    导航条有个手势属性,我们可以替换掉它


    根据打印我们可以看到该手势的方法名为handleNavigationTransition:
        NSArray *targetsArr =  [self.navigationController.interactivePopGestureRecognizer valueForKey:@"targets"];
        id target = [[targetsArr lastObject] valueForKey:@"target"];
        SEL actionSEL = NSSelectorFromString(@"handleNavigationTransition:");
        
        UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:target action:actionSEL];
        
    //    [self.navigationController.interactivePopGestureRecognizer.view addGestureRecognizer:panGesture]; // 导航栏侧滑手势都被替换掉
        [self.view addGestureRecognizer:panGesture];//只是当前页面被替换掉
    

    通过系统的代理模式来写转场动画效果

    导航条有自己的代理UINavigationControllerDelegate,我们可以重写它的代理方法来做操作,不然就是执行系统自己的动画效果

    
    - (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                       animationControllerForOperation:(UINavigationControllerOperation)operation
                                                    fromViewController:(UIViewController *)fromVC
                                                      toViewController:(UIViewController *)toVC  NS_AVAILABLE_IOS(7_0);
    

    其中fromVC为当前控制器,toVC为目标控制器
    我们重写这个方法时,由于返回的是一个支持UIViewControllerAnimatedTransitioning协议的对象,而系统没有,所以我们需要自己写一个继承自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;
    
    

    而最后一个方法里面又有一个协议UIViewControllerContextTransitioning,我们可以在协议中看到传到这个方法的对象transitionContext的一些属性和一些方法。
    在最后一个方法打断点输出如图:


    其中他的containerView属性对象其实就是导航条的view—— UIViewControllerWrapperView。并通过viewControllerForKey拿到当前控制器和目标控制器,然后我们用拿到的这些控制器在这个方法中实现自己的动画效果。
    代码如下:
    继承自UINavigationController的类

    #import "EOCNavigationCtr.h"
    #import "EOCNavAnimation.h"
    
    @interface EOCNavigationCtr ()<UINavigationControllerDelegate, UIGestureRecognizerDelegate>{
        
        UIPercentDrivenInteractiveTransition *_eocAnimaTransProgress;
    }
    
    @end
    
    @implementation EOCNavigationCtr
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.delegate = self;
        
        UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleNavTransition:)];
        panGesture.delegate = self;
        [self.view addGestureRecognizer:panGesture];
        
    }
    
    - (void)handleNavTransition:(UIPanGestureRecognizer*)gesture{
        
        CGPoint gapPoint = [gesture translationInView:gesture.view];
        //[gesture setTranslation:CGPointZero inView:gesture.view];
        float width = gapPoint.x;
        float percent = width/[UIScreen mainScreen].bounds.size.width; // 动画完成进度
        
        if (gesture.state == UIGestureRecognizerStateBegan) {
            
            _eocAnimaTransProgress  = [[UIPercentDrivenInteractiveTransition alloc] init];
            [self popViewControllerAnimated:YES];
            
        }else if(gesture.state == UIGestureRecognizerStateChanged) {
            [_eocAnimaTransProgress updateInteractiveTransition:percent];  // 更新进度
        }else{
            
            if (percent > 0.5) {
                [_eocAnimaTransProgress finishInteractiveTransition];  // 大于百分之五十就完成这个动画
            }else{
                [_eocAnimaTransProgress cancelInteractiveTransition];  // 小于百分之五十就不完成这个动画
            }
            _eocAnimaTransProgress = nil;  // 置空
        }
    }
    
    // 转场动画
    - (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                                animationControllerForOperation:(UINavigationControllerOperation)operation
                                                             fromViewController:(UIViewController *)fromVC
                                                               toViewController:(UIViewController *)toVC{
        
        if (operation == UINavigationControllerOperationPop) {  // 操作为pop
            EOCNavAnimation *eocNav = [[EOCNavAnimation alloc] init];
            eocNav.nav = self;
            return eocNav;
        }
        return nil;
    }
    
    // 控制动画进度
    - (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                                       interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController{
        
        if ([animationController isKindOfClass:[EOCNavAnimation class]]) {  //如果是这个动画就返回它的动画进度
            return _eocAnimaTransProgress;
        }
        return nil;
    }
    
    - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
        /**
         *  这里有两个条件不允许手势执行,1、当前控制器为根控制器;2、如果这个push、pop动画正在执行(私有属性)
         */
        return self.viewControllers.count != 1 && ![[self valueForKey:@"_isTransitioning"] boolValue];
    }
    @end
    

    继承自UIViewControllerAnimatedTransitioning协议的对象

    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    
    @interface EOCNavAnimation : NSObject<CAAnimationDelegate,UIViewControllerAnimatedTransitioning>{
        id <UIViewControllerContextTransitioning> _transitionContext;
    }
    @property (nonatomic, strong)UINavigationController *nav;
    @end
    
    #import "EOCNavAnimation.h"
    
    @implementation EOCNavAnimation
    
    
    - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext{
        
        return 0.5;  // 返回动画时间
        
    }
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
        
        _transitionContext = transitionContext;
        
        UIView *containView = transitionContext.containerView;
        
        UIViewController *fromViewCtr = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        UIViewController *toViewCtr = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        
        [containView insertSubview:toViewCtr.view belowSubview:fromViewCtr.view];
        
        /*
        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
            fromViewCtr.view.transform = CGAffineTransformMakeTranslation([UIScreen mainScreen].bounds.size.width, 0);
        } completion:^(BOOL finished) {
             [_transitionContext completeTransition:![_transitionContext transitionWasCancelled]];
        }];
        */
        
        //复杂动画
        CATransition *st = [CATransition animation];
        st.type = @"cube";
        st.subtype = @"fromLeft";
        st.duration = 0.5;
        st.removedOnCompletion = NO;
        st.fillMode = kCAFillModeForwards;
        st.delegate = self;  // 设置代理,在代理方法中做完成标识,不然返回后不能做任何操作
        [containView.layer addAnimation:st forKey:nil];
        [containView exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
    }
    //  [transitionContext completeTransition:YES]; 没有做completeTransition 完成标识,任何动作都认为在做转场
    - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
        [_transitionContext completeTransition:![_transitionContext transitionWasCancelled]];
    }
    - (void)animationEnded:(BOOL) transitionCompleted{
        NSLog(@"animationEnded");
    }
    @end
    

    参考文章
    iOS导航栏的正确隐藏方式

    相关文章

      网友评论

          本文标题:导航的相关问题

          本文链接:https://www.haomeiwen.com/subject/kzwwattx.html