美文网首页将来跳槽用iOS开发进阶iOS开发攻城狮的集散地
iPhone X上push页面时tabbar“向上偏移”解决思路

iPhone X上push页面时tabbar“向上偏移”解决思路

作者: 择势量投 | 来源:发表于2018-03-21 20:58 被阅读60次

    iPhone X上push页面时tabbar“向上偏移”解决思路大全

       休假回来,又开始了满负载的工作,浪完了就要好好努力!双十一都过了,果粉的iPhone X 都到手了吗?听说iPhone X也遭遇了各种门( ˇˍˇ ),心疼宝宝们!

       iPhone X 作为全面屏的过渡机型,奇葩的屏幕,造成许多的适配问题,其实苹果系统也还没有完全适配iPhone X。今天着重探讨下iPhone X上push页面时tabbar向上偏移的问题,提供几种解决思路,方便iOS开发者尽快适配,天冷了早点下班哈!

    思路一:重写UITabBar的setFrame方法
    思路二:在push页面的统一方法中调整TabBar的frame
    思路三:在viewWillDisappear中调整TabBar的frame

    一、问题解析

       这个问题要追溯到iOS7 中的UITabBar/UINavigationBar的translucent,官方API我都不贴了,简而言之,这个BOOL属性能控制UITabBar/UINavigationBar的半透明效果,默认为YES,即默认情况下为半透明效果,主要体现为被半透明效果处理后产生的色差和添加的view控件的坐标变动。

    1、追根溯源UITabBar之translucent

       当UITabBar/UINavigationBar 默认为半透明状态时,按照苹果系统的UILayoutGuide(iOS7) 和SafeArea(iOS9)布局,则不会存在UIScrollView 和 UITabBar的向上偏移问题,在此不再展开,具体可以参照完美适配iOS11和iPhone X上的两套方案

       当UITabBar/UINavigationBar为不透明时,布局需要手动计算约束或frame,并且起始位置(0,64)或(0,88)。在iPhone X 上safeArea的安全区域是不包含homeIndicator的,当从有TabBar的页面push进入无TabBar的页面时(tabbar不隐藏则frame不变化,无影响),因为系统已经将TabBar自动拓展到homeIndicator区域,已超出安全区域,问题来了,因TabBar从有到无的过程中,系统调整了TabBar的frame,然新的计算规则是参照safeArea计算布局,😓尴尬的事情发生了,为了保证TabBar在安全区域内,系统开始“修正”TabBar的Y坐标(系统添加了frame变化动画),视觉效果是“向上偏移”。

       TabBar偏移后,屏幕底部会有黑色区域,这主要是因为项目的UIAppDelegate的window的背景颜色系统默认为黑色,请将self.window.backgroundColor = tabBarBackgroundColor; 即可解决。

       UITabBar的superView是个很有意思的视图类UILayoutContainerView。在iOS7下UIApplication的 Window的subView的第一个view一定是UILayoutContainerView,而它的nextResponder就是一个ViewController,这是为什么能给通过Window找到ViewController的原因。在iOS8中,一旦使用了presentViewController,而presentViewController的UIApplication的Window的subView的第一个view就变成了UITransitionView,它的nextResponder还是一个Window,在iOS8之下,其实Window的UILayoutContainerView被偷偷藏在了Window的subView的第一个view的一个叫做subviewCache的数组里面,可以利用Runtime获取了这个subviewCache数组里面的UILayoutContainerView。

    2、UILayoutContainerView和UITabBar的frame变化历程

    我们通过创建UITabBar分类方法,重写- (void)setFrame:(CGRect)frame方法。参考代码:

    - (void)setFrame:(CGRect)frame
    {
    NSLog(@"UILayoutContainerView_Frame %@",NSStringFromCGRect(self.superview.bounds));
    NSLog(@"UITabBar_frame %@",NSStringFromCGRect(frame));
    [super setFrame:frame];
    }
    

    启动过程中的打印结果:

    16:09:01.027642+0800 UILayoutContainerView_Frame {{0, 0}, {0, 0}}
    16:09:01.027845+0800  UITabBar_frame {{0, 0}, {375, 812}}
    16:09:01.028843+0800  UILayoutContainerView _Frame {{0, 0}, {0, 0}}
    16:09:01.028984+0800  UITabBar_frame {{0, 0}, {375, 49}}
    16:09:01.029139+0800  UILayoutContainerView _Frame {{0, 0}, {0, 0}}
    16:09:01.029311+0800  UITabBar_frame {{0, 763}, {375, 49}}
    16:09:01.249196+0800  屏幕未知方向
    16:09:01.253602+0800  UILayoutContainerView _Frame {{0, 0}, {375, 812}}
    16:09:01.253776+0800  UITabBar_frame {{0, 763}, {375, 83}}
    16:09:01.254690+0800  UILayoutContainerView _Frame {{0, 0}, {375, 812}}
    16:09:01.254859+0800  UITabBar_frame {{0, 729}, {375, 83}}
    16:09:01.863302+0800  UILayoutContainerView _Frame {{0, 0}, {375, 812}}
    16:09:01.863969+0800  UITabBar_frame {{0, 729}, {375, 83}}
    16:09:01.864481+0800  UILayoutContainerView _Frame {{0, 0}, {375, 812}}
    16:09:01.864594+0800  UITabBar_frame {{0, 729}, {375, 83}}
    

       我们可以看到UILayoutContainerView_Frame 在系统未获取屏幕前是{{0, 0}, {0, 0}},获取屏幕后{{0, 0}, {375, 812}}。UITabBar_frame的变化更多,他都经历了什么,给各位读者留个小话题,我们知道最终显示是正确的。

    来Push 到无tabbar 的页面试下,打印结果:

    17:08:20.098249+0800 设置vc.hidesBottomBarWhenPushed = YES
    17:08:20.100111+0800    即将push
    17:08:20.101656+0800   UILayoutContainerView_Frame {{0, 0}, {375, 812}}
    17:08:20.101877+0800   UITabBar_frame {{0, 695}, {375, 83}}
    17:08:20.104012+0800   push后
    17:08:20.732714+0800  UILayoutContainerView_Frame {{0, 0}, {375, 812}}
     17:08:20.733011+0800   UITabBar_frame {{0, 695}, {375, 83}}
    

    来来,在Pop回到原来页面,打印结果:

    16:42:01.150974+0800   UILayoutContainerView_Frame {{0, 0}, {375, 812}}
    16:42:01.151148+0800    UITabBar_frame {{0, 729}, {375, 83}}
    16:42:01.692267+0800   UILayoutContainerView_Frame {{0, 0}, {375, 812}}
    16:42:01.692640+0800  UITabBar_frame {{0, 729}, {375, 83}}
    

       我们对比整个过程,可以看到UITabBar_frame的Y 坐标从正确值 729 ,“向上移动34”到安全区域,再次出现时,系统重新计算得到正确值729。

       问题比较明显,是在VC.hidesBottomBarWhenPushed = YES 动作发起后,系统会根据hidesBottomBarWhenPushed属性计算UITabBar的frame,并重置frame。

    二、解决问题

       我们已确定UITabBar的frame的变化是发生在VC.hidesBottomBarWhenPushed = YES之后,这个时刻之后,系统开始根据控制器属性hidesBottomBarWhenPushed来调整UITabBar的frame,iPhone X上tabbar的frame开始“不正确”(错误是相对的),我们开始push页面,同时原页面开始调用- (void)viewWillDisappear:(BOOL)animated方法,等完成页面场景转换。

    1、思路一:重写UITabBar的setFrame方法

       我们可以通过继承自系统的UITabBar自定义YGTabBar子类,重写系统的setFrame方法,参考如下:

    - (void)setFrame:(CGRect)frame
    {
    if (IS_IPHONE_X_YG &&
    self.superview &&
    CGRectGetMaxY(self.superview.bounds) > 0 &&
    frame.origin.y != CGRectGetHeight(self.superview.bounds) -CGRectGetHeight(frame)) {
    frame.origin.y = CGRectGetHeight(self.superview.bounds) -CGRectGetHeight(frame);
    }
    [super setFrame:frame];
    }
    

       继承系统的UITabBar后,需要将系统的UITabBarController的UITabBar替换为YGTabBar,参考代码

    UITabBarController *tabBarVC = [[UITabBarController alloc] init];
    [tabBarVC setValue:[[YGTabBar alloc] init] forKey:@"tabBar"];
    

       其实,我们也可以通过创建UITabBar+RefreshFrame的分类,重写setFrame来实现,不过不建议使用分类重写系统方法,会影响所有UITabBar的类甚至引起很难定位的bug问题,这不一定是我们想要的。

    2、思路二:在push页面的统一方法中调整TabBar的frame

       必须在hidesBottomBarWhenPushed 设置之后再修改tabBar的frame,注意此时,vc可能取不到self.tabBarController,需要做校验,参照代码:

    - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
    {
    if (self.viewControllers.count > 0) {
    viewController.hidesBottomBarWhenPushed = YES;
    }
    [super pushViewController:viewController animated:animated];
    if (IS_IPHONE_X_YG && self.tabBarController) {
    CGRect frame = self.tabBarController.tabBar.frame;
    if (!CGRectEqualToRect(frame, CGRectZero) &&
    frame.origin.y != CGRectGetHeight([UIScreen mainScreen].bounds) -CGRectGetHeight(frame)) {
    frame.origin.y = CGRectGetHeight([UIScreen mainScreen].bounds) -CGRectGetHeight(frame);
    }
    self.tabBarController.tabBar.frame = frame;
    }
    }
    

    3、思路三:在viewWillDisappear中调整TabBar的frame

       必须在hidesBottomBarWhenPushed 设置之后再修改tabBar的frame,注意此时,vc可能取不到self.tabBarController,需要做校验,参照代码:

    - (void)viewWillDisappear:(BOOL)animated
    {
    // 必须在hidesBottomBarWhenPushed 设置之后再修改tabBar的frame,注意此时,vc可能取不到self.tabBarController
    if (IS_IPHONE_X_YG && self.tabBarController) {
    CGRect frame = self.tabBarController.tabBar.frame;
    if (!CGRectEqualToRect(frame, CGRectZero) &&
    frame.origin.y != CGRectGetHeight([UIScreen mainScreen].bounds) -CGRectGetHeight(frame)) {
    frame.origin.y = CGRectGetHeight([UIScreen mainScreen].bounds) -CGRectGetHeight(frame);
    }
    self.tabBarController.tabBar.frame = frame;
    }
    [super viewWillDisappear:animated];
    }
    

    三、思路对比

    看了以上三个思路,我们具体怎么用呢,给几点参考建议。

       1、思路一对项目的耦合性更低,更易调整,若已继承UITabBar并自定义,只需重写方法即可。若未曾有自定义tabbar类,要权衡下项目使用分类还是继承的必要性,分类是否与其他分类有冲突等问题,这个问题是有UITabBar引起的应交个他自己或其分类或子类来管理,推荐思路一。

       2、若项目中对push操作或有UINavBarController基类,可以使用思路二,减少新建分类,利于项目的维护,需要注意并不是所有的vc都能取到self.tabBarController,需要做校验。

       3、思路三,可以解决具体页面的偏移问题,但是需要多处设置,不推荐。

    如有不当之处或有更好思路,请@我,谢谢😘

    相关文章

      网友评论

      • chen文:方案一解决了自定义的问题。赞

      本文标题:iPhone X上push页面时tabbar“向上偏移”解决思路

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