来聊聊ios下自定义UINavigationBar及UITabBar的问题

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及UITabBar的问题)