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

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、思路三,可以解决具体页面的偏移问题,但是需要多处设置,不推荐。

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

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

推荐阅读更多精彩内容