iOS11缩小界面导航栏与标签栏异常的问题

2,212 阅读12分钟

到底是在什么情况下出现怎么样的异常?

几个月之前自己尝试封装了一个仿QQ抽屉效果的轮子,相比较目前常见的抽屉框架优势还是比较明显的,用的人也慢慢的多起了来,现在基本已经稳定了。如果有兴趣可以打开🔗一行代码集成0耦合侧滑抽屉一看。这个问题就是在这基础上发现的(找遍了百度,谷歌没有找到对应的解决方法),异常如图所示:

当缩小整个tabbarController的View时,导航栏上方会出现一个黑条。有人会说,哎呀我又不用你的框架,和我没关系,不看了。😭我还是建议你看一看,遇到这种网上找不到答案的问题,我们应该如何的去解决他。

如何解决问题

1、定位问题

遇到这种情况首先当然是定位问题出现在哪里,如果问题都无法定位那么连谷歌都不知道从哪个方面谷歌。

  • 首先我认真的查看了我写的代码,看看是否是因为我在动画的过程中做了什么缺心眼的操作。
  • 然后在一些关键的可疑地方注释掉自己觉得可能会导致这种情况的代码运行看看是否会有所改善。

经过一系列排查,发现代码没啥问题,并且这个异常只会在iOS11下且缩放的时候才表现出来,为了确认自己的想法,我新建了一个测试工程,单单对界面进行缩放,分别在iOS10和iOS11下进行测试(为了证明这是苹果自己的问题),测试程序非常简单,就是在touchBeagn时用动画将界面缩小一点点,然后得到的测试结果如下:

前面两个是iOS11,第三个是iOS10,非常的明显,两个iOS11的程序的导航栏的头部都出现了一段空的异常。

2、自己思考解决方案+谷歌\百度

当我们知道这个问题是大概是出现在什么场景之后,可以先自己思考一下有没有好的解决方式,然后再到网上找一下相关的解决方式。除非你已经有经验或者特别自信,不然就算你知道怎么解决了,我其实也建议你到网上搜一下别人的解决办法,说不定更好也能让你更清晰。

然后我就是想不到什么好的解决方法,谷歌百度了一个下午没有找到对应的案例,出现这样的情况的实在太少了,最终我甚至在stackflow上搜索 iOS11 navigation、iOS11 tabbarController,把所有的搜到问题都过了一遍都没这个相关的问题。最终我放弃了,决定自己尝试去解决它。

3、自己动手kill he

说到自己解决这个问题,你会怎么做呢?说一下我的方式

3.1 掌握一些基本的知识

既然是只在iOS11出现,我首先会想到了解iOS11对导航栏到底做了怎么样的修改,于是我在网上翻阅了一些优秀的关于iOS11更新的文章去理解iOS11到底对导航栏做了什么修改?(虽然里面没有我们问题的答案,但是我们非常有必要的先把基础的东西弄明白再来解决问题,这将使你少走很多弯路)具体做了哪些调整这里就不多说了,搜一下一大把~

3.2 定位问题并开始进行尝试

在掌握了一些基本的知识之后,我开始尝试着想解决办法,首先对界面层级进行分析,界面层级如下图,淡蓝色为我鼠标选中的view:

看到这个就发现navigationBar以及他所有的subview高度只有44,然后因为少了状态栏(电池栏)20的高度就把后面界面的背景色给漏出来了,于是我尝试把后面的view的背景设为与导航栏同样的颜色,异想天开的觉得反正你看到后面了,那我把后面弄成一模一样的颜色你就看不出来了吧,发现没卵用一点变化都没,然后在此基础上我又尝试将translucent设置为NO(想到啥就做啥多试试没毛病),发现有点效果,但是颜色有明显的偏差(如下图)。而且这样做就算颜色没偏差我们也不能这么做,因为修改translucent会导致界面布局的原点发生改变,说不定就要做更多的工作了。

3.2 仔细确认问题出现在哪!

果然邪门歪道总是靠不住的,我们还是正儿八经的去解决它吧,我们先比较一下iOS11,在正常情况下导航栏的subview的高度情况以及在缩放后导航栏的高度情况,然后再比较缩放后在iOS10下导航栏的subview的布局情况,再决定到底要修改什么地方。首先研究一下iOS11下导航栏的subview的层级关系:

从UINavigationBar开始往下就是我们能看见的上面64高度(X另算)的导航栏。 然后研究一下iOS11正常的情况下导航栏的布局:
这层选中的橘色为_UIBarBackgroud,起始点为-20,高度为64,从前面的层级关系图中可以了解到他是放在UINavigationBar上的。然后我们再来看下上面这张图,非常明显,只有橘色的这一层有64高,其他的包括他的父视图都只有44高,于是我们可以明白, 在正常情况下,状态栏的20个像素背后我们看到的就是_UIBarBackgroud这层东西,这个非常关键。

再来研究缩放后会异常的主界面布局:

于是我们发现_UIBarBackgroud这个东西起始位置是0且高度只有44了,和正常情况比较有明显的偏差,所以导致我们无法从状态栏的位置再看到_UIBarBackgroud这个东西,所以界面显示异常了

虽然我们可以知道估计就是这个问题影响的,但是我们更保险的得再确认一下iOS10缩放之后这层的高度是否真的是64,毕竟缩放后iOS10是正常的,它具有比较权威的解释。

然后我们发现,在iOS10下,_UIBarBackgroud的起始位置为-20,高度为64,这是我们想要的结果,于是我们可以肯定的说:哎,苹果爸爸你iOS11的毛病还挺多的

3.3 定位之后,动手解决它!

到了这里,我们的目标就非常明确了,我们只要想办法把_UIBarBackgroud这个玩意的高度和起始位置做对应的调整就OJBK了,但是~

不过咱们不管可达鸭眉头皱没皱,先搞下去再说。于是我们在导航控制器的viewDidLayoutSubviews写下以下代码来修改_UIBarBackgroud的高度

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    if (@available(iOS 11, *)) { // xcode9新特性 可以这样判断,xcode9以下只能用UIDevice systemVersion 来判断
        UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
        CGFloat statusH = CGRectGetHeight(statusBar.frame);
        for (UIView *view in self.navigationBar.subviews) {
            // 通过遍历获取到_UIBarBackground图层,修改其frame
            if ([NSStringFromClass([view class]) isEqualToString:@"_UIBarBackground"]) {
                CGRect frame = view.frame;
                frame.size.height = 44 + statusH;
                frame.origin.y = -statusH;
                view.frame = frame;
            }
        }
    }
}

写好啦,皆大欢喜欢喜的运行起我们的测试程序,发现还是一个卵样。。压根儿没用,于是。。。回忆刚开始的翻阅stackflow的问题时帮助了我,我清晰的记得有一个问题是说自定义的导航栏高度没法修改,里面的解决方案提到了要用约束做修改,于是我选择用约束去改变他,我在上面的代码里加上了约束的代码。当然我们不可能再去用masonry这种框架,只能用苹果自带的代码约束咯。

NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:44 + statusH];
[view addConstraint:heightConstraint];

然后。运行。发现不仅没卵用,还给我报这个约束的警告:

从控制台打印大概看出来是说有一个NSAutoresizingMaskLayoutConstraint这个鬼约束设置了高度是44,然后我新加的高度是64就约束冲突了。。。。。excuse me??前面那个是个啥鬼东西?没见过,赶紧去网上查一下。最后发现这个东西大概就是苹果自动帮我们布局时做的一些约束,这个东西可以通过UIView的translatesAutoresizingMaskIntoConstraints这个属性设置为NO关掉,我尝试过关掉,关掉之后布局就完全乱了,除非什么都自己去拉约束布局,这样当然不是一种好的解决方式;于是我们修了一下我们自己添加的约束的优先级来消除警告,

// 这个有4个等级的优先级,这里就不做详细介绍了,可以自行在网上搜一下
heightConstraint.priority = UILayoutPriorityDefaultHigh;

然后⚠️警告就没有了,但是还是没卵用。。。什么情况?我明明按照所有的要求都做了且保证代码都有运行。。连个高度都改变不了吗??于是我疯了!在viewWillLayoutSubviews,ViewWillAppear,ViewDidAppear,ViewDidLoad方法内全部给粘贴了这个代码。运行之后。。神奇了,TM居然正常了?? 再来一百个excuse me??表达一下我的心情

于是我开始做苦力活,把一些不必要代码删掉,一边删一边运行,最后保留了NavigationController类里买呢两个方法内的部分代码,这样就可以保证显示正常了。代码如下:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
//    return;
    if (@available(iOS 11, *)) { // xcode9新特性 可以这样判断,xcode9以下只能用UIDevice systemVersion 来判断
        UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
        CGFloat statusH = CGRectGetHeight(statusBar.frame);
        for (UIView *view in self.navigationBar.subviews) {
            if ([NSStringFromClass([view class]) isEqualToString:@"_UIBarBackground"]) {
                NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:44 + statusH];
                heightConstraint.priority = UILayoutPriorityDefaultHigh;
                [view addConstraint:heightConstraint];
            }
        }
    }
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    if (@available(iOS 11, *)) { // xcode9新特性 可以这样判断,xcode9以下只能用UIDevice systemVersion 来判断
        UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
        CGFloat statusH = CGRectGetHeight(statusBar.frame);
        for (UIView *view in self.navigationBar.subviews) {
            // 通过遍历获取到_UIBarBackground图层
            if ([NSStringFromClass([view class]) isEqualToString:@"_UIBarBackground"]) {
                CGRect frame = view.frame;
                frame.size.height = 44 + statusH;
                frame.origin.y = -statusH;
                view.frame = frame;
            }
        }
    }
}

分别在viewWillLayoutSubviews、viewDidLayoutSubviews添加约束以及改变frame。注意:他们的方法互换或者将代码写到一起都试过没用,只有这样才有用!为啥?不知道,试出来的。。

3.4 解决之后的结果!

3.5 其他问题

还有我们发现在界面缩小的时候iPhoneX的tabbar的高度也会变小,于是我们使用同样的思路调整了一下tabbar的高度,在自定义的tabbarController内,代码如下:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    if (!iPhoneX) return;
    CGRect frame = self.tabBar.frame;
    CGFloat h = 83 - frame.size.height;
    frame.size.height += h;
    frame.origin.y -= h;
    self.tabBar.frame = frame;
}

4、使用我们的框架时如何解决这个问题?

第4点这一段才是向我提issue的同学最关心的吧,毕竟前面巴拉巴拉废话一大堆没啥用的。因为我们的demo与新建的测试工程有一点点差别,所以我在框架的demo工程里面稍微整合了一下,解决方案如下:

  • 解决navigationbar上面20像素的黑条的方法在NavigationController的viewDidAppear方法内,如果整个导航栏颜色偏黑可以尝试将tabbarController.view.backgroundColor = [UIColor whiteColor];背景设置为白色。
  • 解决iPhoneX的tabbar高度变动的问题,在TabbarController类的viewDidAppear、viewDidLayoutSubviews这两个方法内。

最后再提供一种解决缩放时navigationbar上面20像素的黑条的问题,这个是我无意中测试发现的,也是最最最最简单的。就是给navigationBar设置一个背景图片BackgroundImage,代码如下:

UINavigationBar *naviBar = [UINavigationBar appearance];
[naviBar setBackgroundImage:[UIImage imageNamed:@"nav_bg"] forBarMetrics:UIBarMetricsDefault];

demo的导航栏为白色,nav_bg也为一张白色的图。只要设置了背景图片,缩放时就不会有问题了。你可以运行我的demo两种方式都尝试一下。。当然最后一种是最简单的。

总结

通过上面的案例,我们知道要解决一个问题,如果有正常和异常的两种情况,最好就是比较两个的差别在哪。就像平时开发中,上一个版本是正常的而新版本异常,在排查问题时也可以将两者比较来获取解决方式,以及屏蔽掉一些可疑的代码再运行看是否是这段可疑代码导致的异常,这些都是帮助我们定位问题的好办法

当然大部分的开发者都是这样做的,我在这里说这些。。主要是因为有这个文章所说的的问题会遇到的人太少了,不说点东西大家看完之后可能会说,卧槽,看了白看,反正我这辈子可能都不会出现这种情况。。哈哈

最后如果你想问我,解决上面的问题为啥要在这个方法里写不能在这个方法里?那么我会告诉你:我瞎JB试,试出来的😂......所以没事多试试,试试又不会怀孕。。

最后一波广告,如果你的项目需要抽屉侧滑的功能,欢迎看看我的轮子

一行代码实现0耦合侧滑抽屉

毕竟像我这样,因为某个童鞋的一个issue而写了一篇文章来解决的负责男人来说,你还是值得去看一下的😂。ps:其实和负责卵关系没有,因为每一个issue都是我提升的机会,所以你助我提升,我助你轻松,相互合作🤝各取所需😄这是这个社会生存的真理。。