美文网首页iOS开发
来聊聊ios下自定义UINavigationBar及UITabB

来聊聊ios下自定义UINavigationBar及UITabB

作者: 梦回蓝桥 | 来源:发表于2018-06-05 11:48 被阅读95次

    demo主要包含以下几个模块

    1. 自定义UINavigationBar、UITabBar;
    2. 每一个UIViewController对应一个UINavigationController的封装;
    3. UITabBar凸出按钮的处理;
    4. 影响页面布局的几种属性探究;

    先来看看项目中的实际效果图:


    fhgif.gif

    然后看看demo中的效果图:


    home_gif.gif demo_gif.gif
    一. 每个ViewController配置一个UINavigationController
    1. 首先我们需要一个导航控制器类SXNavigationController

    实际vc的导航控制器,并配置一些基本信息(如侧滑返回等)

    2. 然后需要一个SXWrapViewController类

    对所有实际vc(如SXHomeViewController)进行包装
    所有页面最终都转换成 SXWrapViewController

    3. 一个SXWrapNavigationController

    不同SXWrapViewController对应的不同导航控制器类

    最终我们push到的页面是SXWrapViewController类(对vc进行包装后的类),而不是实际的vc类,它的导航控制器是SXWrapNavigationController(单独对应),修改navigationbar也就是对SXWrapViewController对应的SXWrapNavigationController.navigationBar进行的修改

    核心代码如下:

    SXWrapNavigationController.m
    - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
       
        //设置vc的导航控制器
        viewController.sx_navigationController = (SXNavigationController *)self.navigationController;
        if (self.viewControllers.count > 0) {
            UIBarButtonItem *leftBarButtonItem = [UIBarButtonItem barButtonItemWithTarget:self andWithAction:@selector(didTapBackButton) andWithImage:@"Return"];
            viewController.navigationItem.leftBarButtonItem = leftBarButtonItem;
            viewController.hidesBottomBarWhenPushed = YES;
        }
        /* push 到的页面 是 SXWrapViewController 类 (对实际vc进行的包装) */
        [self.navigationController pushViewController:[SXWrapViewController wrapViewControllerWithViewController:viewController] animated:animated];
    }
    
    
    SXWrapViewController.m
    /*
     * 不同vc对应单独UINavigationController的核心实现
     * 实际vc(viewController) 最终都转换成 SXWrapViewController 进行包装
     * SXWrapViewController的导航控制器是 SXWrapNavigationController
     * 所以你在vc界面进行的navigationBar操作 都是对当前SXWrapViewController的导航控制器SXWrapNavigationController进行的(就此实现了vc与navi的单独对应)
     * @param viewController 实际vc
     *
     */
    + (SXWrapViewController *)wrapViewControllerWithViewController:(UIViewController *)viewController {
    
        //单独控制实际vc
        SXWrapNavigationController *wrapNavigationController = [[SXWrapNavigationController alloc] init];
        wrapNavigationController.viewControllers = @[viewController];
    
        //载体 push的vc最终都转化成了SXWrapViewController,SXWrapViewController的导航控制器是SXWrapNavigationController
        SXWrapViewController *wrapViewController = [[SXWrapViewController alloc] init];
        [wrapViewController.view addSubview:wrapNavigationController.view];
        [wrapViewController addChildViewController:wrapNavigationController];
    
        return wrapViewController;
    }
    


    这样我们就实现了每个viewController对应一个UINavigationController

    那么下面就是对UINavigationBar的操作了

    首先我们来看看navigationBar的层级结构(不同的ios系统层级结构不同)

    ios9-10下的层级结构:


    ios9-10.png

    iOS11下的层级结构,这里我就不贴图了

    _UINavigationBarBackground -> _UIBarBackground
    同时_UIBarBackground下除了原先的UIImageView(下划线)又多了一层UIImageView

    ios11.2.6的时候跟ios11.1没多大差别,就是标题view由
    UINavigationItemView -> UINavigationContentView

    另外如果你设置了navigationBar.backgroundColor或者translucent(透明度相关),他的层级结构又是另一番景象了(多2-3层),这里就不做展示了;

    修改navigationBar相关信息以前走了很多弯路,现在记得的要么就是你使用系统方法

    - (void)setBackgroundImage:(nullable UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
    


    要么是向UINavigationBar最上面添加一个自定义view

    if (!self.overlayView) {
        [self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
        self.overlayView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), TOP_HEIGHT)];
        self.overlayView.userInteractionEnabled = NO;
        self.overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
        [[self.subviews firstObject] insertSubview:self.overlayView atIndex:0];
    }
    self.overlayView.backgroundColor = backgroundColor;
    


    最近两个项目我使用的都是第二种方法(各种实现都方便)

    ios9-view.png
    1. 隐藏navigationBar

     self.navigationController.navigationBarHidden = YES;
    

    1. navigationBar颜色渐变

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView
    {
        // 改变自定义view的alpha值实现渐变
        CGFloat alphaRatio = contentOffSetY/(180 - 64) > 1 ? 1 : contentOffSetY/(180 - 64);
        [self.navigationController.navigationBar setOverlayViewAlpha:alphaRatio];
    }
    

    1. 改变navigationBar背景颜色

    //修改的无非就是自定义view的背景色
    [self.navigationController.navigationBar setOverlayViewBackgroundColor:[UIColor purpleColor]];
    


    然后就是UINavigationBar下划线的处理

    [[UINavigationBar appearance] setShadowImage:[UIImage imageWithColor:[UIColor redColor]]];
    


    这里要注意的是只有在setBackgroundImage:forBarMetrics:这个方法实现的情况下,使用setShadowImage:方法修改下划线背景色才会生效(如下声明)

    /* Default is nil. When non-nil, a custom shadow image to show instead of the default shadow image. For a custom shadow to be shown, a custom background image must also be set with -setBackgroundImage:forBarMetrics: (if the default background image is used, the default shadow image will be used).
     */
    @property(nullable, nonatomic,strong) UIImage *shadowImage
    

    二. UITabBar

    UITabBar的自定义没什么可说的,说说一些细节问题吧
    如何实现凸出按钮凸出部分点击事件的触发

    这里写了一个继承自UITabBar的子类SXCustomTabBar,重写了它的- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;方法

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    
        //self.isHidden == NO 说明当前页面是有tabbar的,那么肯定是在导航控制器的根控制器页面
        //在导航控制器根控制器页面,那么我们就需要判断手指点击的位置是否在发布按钮身上
        //是的话让发布按钮自己处理点击事件,不是的话让系统去处理点击事件就可以了
    
        NSLog(@"point: %@",NSStringFromCGPoint(point));
    
        if (self.isHidden == NO) {
        
            //将当前tabbar的触摸点转换坐标系,转换到发布按钮的身上,生成一个新的点
            CGPoint newP = [self convertPoint:point toView:self.tabBarView.centerBtn];
        
            NSLog(@"newpoint:%@",NSStringFromCGPoint(newP));
        
            //判断如果这个新的点是在发布按钮身上,那么处理点击事件最合适的view就是发布按钮
            if ( [self.tabBarView.centerBtn pointInside:newP withEvent:event]) {
                return self.tabBarView.centerBtn;
            }else{//如果点不在发布按钮身上,直接让系统处理就可以了
            
                return [super hitTest:point withEvent:event];
            }
        }else {//tabbar隐藏了,那么说明已经push到其他的页面了,这个时候还是让系统去判断最合适的view处理就好了
            return [super hitTest:point withEvent:event];
        }
    }
    


    然后使用kvc替换掉系统的UITabBar

    SXCustomTabBar *customTabBar = [[SXCustomTabBar alloc]init];
    [self setValue:customTabBar forKeyPath:@"tabBar"];
    


    这样就实现了点击tabbar外部触发点击事件

    修改tabbar上面边线跟navigationbar相同

    //删除tabbar顶部分割线
    [[UITabBar appearance] setShadowImage:[UIImage new]];
    [[UITabBar appearance] setBackgroundImage:[[UIImage alloc]init]];
    


    同理,只有在实现setBackgroundImage:方法的情况下,setShadowImage:才会生效

    其中凸出按钮(centerButton)是单独的一个按钮,因为中心凸出按钮的缘故,我是把shadowImage隐藏了,然后添加了左、右两条横线,宽度使用勾股定理计算sqrt()得出;

    三. 其它详情参见demo

    基本都会满足你现在UINavigationController+UITabBar框架的使用

    相关文章

      网友评论

        本文标题:来聊聊ios下自定义UINavigationBar及UITabB

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