来聊聊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框架的使用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • Java多线程实现方式:Thread,Runnable,Callable多线程是乱序执行Thread===1.Th...
    小颖啊阅读 587评论 0 2
  • UIScrollView(包括它的子类UITableView和UICollectionView)是iOS开发中最常...
    Coder007阅读 347评论 0 0
  • 长相思 一重山,两重山, 山远天高烟水寒,相思枫叶丹。 菊花开,菊花残, 塞雁高飞人未还,一帘风月闲。 长相思 云...
    煜龙小窝阅读 1,063评论 3 2
  • 记忆 像一道闪电 在分手的刹那 拨开我眼前的愁云 那个没有月亮的夜晚 你的泪水倾湿了来时的路 揣着手中的泥泞 忆你...
    不是丽人阅读 262评论 0 0