iOS开发中经常会遇到需要隐藏导航栏的分割线的需求,之前已经有两种方法

方法一

直接设置navigationBar的ShadowImage和BackgroundImag,这样做可以一劳永逸,在当前导航控制器的所有子控制器页面看到的导航栏都是没有分割线的。

弊端:想恢复比较麻烦

[self.navigationController.navigationBar setShadowImage:[UIImage new]];
[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];

方法二

遍历navigationBar的所有子View和子View的子View,最终找到一个UIImageView,然后持有该对象,设置其隐藏或者显示即可达到目的。

弊端:代码繁琐,遍历次数太多。并且iOS10下已经没有效果

if ([self.navigationController.navigationBar respondsToSelector:@selector( setBackgroundImage:forBarMetrics:)]){
NSArray *list=self.navigationController.navigationBar.subviews;
for (id obj in list) {
if ([obj isKindOfClass:[UIImageView class]]) {
UIImageView *imageView=(UIImageView *)obj;
NSArray *list2=imageView.subviews;
for (id obj2 in list2) {
if ([obj2 isKindOfClass:[UIImageView class]]) {
//将分割线 移除
UIImageView *imageView2=(UIImageView *)obj2;
imageView2.hidden=YES;
}
}
}
}
}

方法三(方法二的变种版本)

下面的代码是在iOS10下打印了UINavigationBar对象的所有子View,我们发现之前存放分割线的容器在iOS上已经变为了_UIBarBackground,他是一个私有的类的对象,我们无法直接获得。可以使用NSClassFromString(@"_UIBarBackground")来判断。

2016-12-02 12:29:46.898125 Demo[13941:3014012] <_uibarbackground: frame="(0" userinteractionenabled="NO;" layer="<CALayer:">>,UIView
2016-12-02 12:29:46.900464 Demo[13941:3014012] <>: item=<: title:'数据统计'> title=数据统计>,UIView
2016-12-02 12:29:46.902259 Demo[13941:3014012] >,UIControl
2016-12-02 12:29:46.902681 Demo[13941:3014012] >,UIControl
2016-12-02 12:29:46.904273 Demo[13941:3014012] <_uinavigationbarbackindicatorview: frame="(8" alpha="0;" opaque="NO;" userinteractionenabled="NO;" layer="<CALayer:">>,UIImageView

我们将方法二的代码改为下面这个样子,发现在iOS10上分割线被隐藏了。

if ([self.navigationController.navigationBar respondsToSelector:@selector(setBackgroundImage:forBarMetrics:)]){
NSArray *list=self.navigationController.navigationBar.subviews;
for (id obj in list) {
if ([obj isKindOfClass:NSClassFromString(@"_UIBarBackground")]) {
UIImageView *imageView=(UIImageView *)obj;
NSArray *list2 = imageView.subviews;
for (id obj2 in list2) {
if ([obj2 isKindOfClass:[UIImageView class]]) {
_navLine = (UIImageView *)obj2;
}
}
}
}
}

但是这么做在iOS10之前的版本又不行了,解决版本是把第一次遍历的判断的条件改为[obj isKindOfClass:[UIView class]]。并且需要判断obj2的高度,因为条件更改后会遍历出两个UIImageView。

if ([self.navigationController.navigationBar respondsToSelector:@selector(setBackgroundImage:forBarMetrics:)]){
NSArray *list=self.navigationController.navigationBar.subviews;
for (id obj in list) {
if ([obj isKindOfClass:[UIView class]]) {
UIImageView *imageView=(UIImageView *)obj;
NSArray *list2 = imageView.subviews;
for (id obj2 in list2) {
if ([obj2 isKindOfClass:[UIImageView class]]) {
if (CGRectGetHeight([obj2 frame]) == 0.5) {
_navLine = (UIImageView *)obj2;
}
}
}
}
}
}

方法四

发现以上方法都太过繁琐,所以我把iOS10和之前版本的UINavigationBar的子试图的对象都打印了出来,如下:

iOS9
2016-12-02 12:31:39.201259 Demo[13948:3014909] <_uibarbackground: frame="(0" userinteractionenabled="NO;" layer="<CALayer:">>,UIView
2016-12-02 12:31:39.202724 Demo[13948:3014909] >,UIView
2016-12-02 12:31:39.204211 Demo[13948:3014909] <>: item=<: title:'数据统计'> title=数据统计>,UIView
2016-12-02 12:31:39.205571 Demo[13948:3014909] >,UIView
2016-12-02 12:31:39.207938 Demo[13948:3014909] >,UIControl
2016-12-02 12:31:39.208315 Demo[13948:3014909] >,UIView
2016-12-02 12:31:39.208874 Demo[13948:3014909] >,UIControl
2016-12-02 12:31:39.209393 Demo[13948:3014909] >,UILabel
2016-12-02 12:31:39.209964 Demo[13948:3014909] <_uinavigationbarbackindicatorview: frame="(8" alpha="0;" opaque="NO;" userinteractionenabled="NO;" layer="<CALayer:">>,UIImageView
iOS10
2016-12-02 12:33:59.139 Demo[512:301114] <_uinavigationbarbackground: frame="(0" autoresize="W;" userinteractionenabled="NO;" layer="<CALayer:">>,_UIBarBackgroundImageView
2016-12-02 12:33:59.139 Demo[512:301114] >,UIView
2016-12-02 12:33:59.141 Demo[512:301114] >,UIView
2016-12-02 12:33:59.142 Demo[512:301114] >,UIView
2016-12-02 12:33:59.142 Demo[512:301114] >,UIControl
2016-12-02 12:33:59.143 Demo[512:301114] >,UILabel
2016-12-02 12:33:59.144 Demo[512:301114] >,UIControl
2016-12-02 12:33:59.144 Demo[512:301114] >,UIView
2016-12-02 12:33:59.145 Demo[512:301114] <_uinavigationbarbackindicatorview: frame="(8" alpha="0;" opaque="NO;" userinteractionenabled="NO;" layer="<CALayer:">>,UIImageView

仔细研究后发现,iOS10只是把_UINavigationBarBackground(UIImageView的子类)改为了_UIBarBackground(UIView的子类)。这两个对象虽然不同,但是作用是一样的,他的Y值为-20,猜测作用应该是为了将NavigationBar延伸到状态栏下面。通过打印出来的顺序可以看到不管是_UINavigationBarBackground还是_UIBarBackground,他们都是在UINavigationBar的最底层,也就是最先被addSuview到父View上的。然后他们的唯一一个子View就是UIImageViewe类型的对象,并且该对象的高度就是0.5,可以确定就是分割线。

所以经过精简后,去掉导航栏分割线的代码就变成了下面这个样子。

UIView *backgroundView = [self.navigationController.navigationBar subviews].firstObject;
_navLine = backgroundView.subviews.firstObject;

经过测试,在iOS10和iOS9上都可以隐藏分割线。

欢迎有更好的方法的同学一起交流。