热点共享蓝条下压,导致页面底部按钮显示不全的完美解决方案

简介: 热点共享蓝条下压,导致页面底部按钮显示不全的完美解决方案

手机共享了热点,另一部手机通过wifi连接连接上这个热点。手机顶部会出现一个蓝色的工具栏,并显示”个人热点:1个连接“,这个工具栏比正常的工具栏告20个像素。

当打开一个应用,若该页面不显示系统工具栏,看不到这个热点工具栏;若该页面显示了系统工具栏,那么就可以看到这个高度增加了一倍的工具栏,并且页面的所有空间下移20像素,导致在底部的按钮显示不全。想修改这个问题,主要是如何发现蓝条的显示和消失,来决定是否上移底部的按钮20个像素。系统会根据蓝条的显示和消失,重新布局页面,所以不要移动所有所有控件,一般只需要适配底层一到二个控件就可以。

判断应用状态栏是否显示蓝条可以通过三种方法:

// 标准系统状态栏高度

#define SYS_STATUSBAR_HEIGHT 20

// 热点栏高度

#define HOTSPOT_STATUSBAR_HEIGHT 20

(1).被动方式,当蓝条显示或消失,会发送状态栏变更通知

(UIApplicationWillChangeStatusBarFrameNotification),通过读取通知中状态栏的高度来判断是否显示了栏条。

- (void)handleUIApplicationWillChangeStatusBarFrameNotification:(NSNotification*)notification
{
    [self logViewTreeForMainWindow];
    
    if(notification != nil)
    {
        CGRect newStatusBarFrame = [(NSValue*)[notification.userInfo objectForKey:UIApplicationStatusBarFrameUserInfoKey] CGRectValue];
        // 根据系统状态栏高判断热点栏的变动
        [SingleObject sharedInstance].bPersonalHotspotConnected = (CGRectGetHeight(newStatusBarFrame)==(SYS_STATUSBAR_HEIGHT+HOTSPOT_STATUSBAR_HEIGHT)?YES:NO);
    }
    else
    {
        [SingleObject sharedInstance].bPersonalHotspotConnected = ([UIApplication sharedApplication].statusBarFrame.size.height==(SYS_STATUSBAR_HEIGHT+HOTSPOT_STATUSBAR_HEIGHT)?YES:NO);
    }
    float hotspotConnectedOffsetY = 0;
    if(([SingleObject sharedInstance].bPersonalHotspotConnected) && (_listenOrderPanel != nil))
    {
        hotspotConnectedOffsetY = -HOTSPOT_STATUSBAR_HEIGHT;
}
//调整控件的frame
}

(2).主动方式,通过直接读取系统状态栏的高度([UIApplication sharedApplication].statusBarFrame.size.height)来判断是否显示了热点蓝条。

- (void)applicationDidBecomeActive:(UIApplication *)application {
    BOOL bPersonalHotspotConnected = ([UIApplication sharedApplication].statusBarFrame.size.height==(SYS_STATUSBAR_HEIGHT+HOTSPOT_STATUSBAR_HEIGHT)?YES:NO);
    [SingleObject sharedInstance].bPersonalHotspotConnected = bPersonalHotspotConnected;
    if([SingleObject sharedInstance].isInYXCenterViewController)
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:CHANGE_STATUS_BAR_FRAME_NOTIFICATION object:nil];
    }
}

(3).主动获取,通过检查keyWindow的倒数第二个图层的初始y坐标是否为20像素来判断是否显示热点蓝条

+(BOOL)getPersonalHotspotConnected
{
    UIView *keyWindowView = [[UIApplication sharedApplication] keyWindow];
    if((keyWindowView != nil) && ([keyWindowView subviews].count > 1))
    {
        UIView *UILayoutContainerView0 = [keyWindowView subviews][1];
        if(UILayoutContainerView0.frame.origin.y == HOTSPOT_STATUSBAR_HEIGHT)
        {
            [SingleObject sharedInstance].bPersonalHotspotConnected = YES;
        }
        else
        {
            [SingleObject sharedInstance].bPersonalHotspotConnected = NO;
        }
    }
    return [SingleObject sharedInstance].bPersonalHotspotConnected;
}

既然知道了如何判断是否显示了热点蓝条,剩下的是如何捕获热点蓝条显示和消失的时机,进而进行实时调整底部的控件的偏移就可以了。热点蓝条显示和消失的情景如下:

1.应用在前台切换到后台时会收到状态栏变更通知,蓝色状态栏通知(状态栏高度为正常的20像素),就是应用在后台,你的应用是没有热点蓝条的。

2.应用在后台,操作系统热点蓝条消失,若应用非定位服务的应用可能你收不到状态栏变更通知,定位服务的应用会收到状态栏变更通知。

3.应用在后台,操作系统热点蓝条出现,若应用非定位服务的应用可能你收不到状态栏变更通知,定位服务的应用应该会收到状态栏变更通知。

4.当应用从后台切换到前台时,会收到状态栏改变通知(状态栏高度为40像素)。

5.当操作系统已经显示蓝条,应用开始启动,你收不到状态变更通知。你想正确调整底部控件,只能主动获取状态栏的高度进行是否显示了热点蓝条,进而调整底部控件。

6.启动时没有热点蓝条,当应用在前台,有人接入热点,显示热点蓝条,会收到状态栏变更通知。当应用在前台,显示热点蓝条,后蓝条消失,会收到状态栏变更通知。

5.当操作系统已经显示蓝条,应用开始启动后,有人接入热点,显示热点蓝条,会收到状态栏变更通知。当应用在前台,显示热点蓝条,后蓝条消失,会收到状态栏变更通知。

可以看到应用在前台时,都是能够收到系统的状态栏变更,只需要被动的接收系统的状态栏变更通知,再调整控件就可以了。

当应用在后台,经过多次热点蓝条的消失和显示,你可能漏掉正确的调整空间的时机。

真对这种情况,你只需要在应用进入前台时,通知相关需要调整的页面调整一下底部控件就可以了,如:(2)主动式获取状态栏的处理。

你处理了这两类情况,你就解决了绝大多数情况。但是还有一种异常情况,咱们看一下:

当然当应用启动前手机已经显示了蓝条,有可能(我的出租车司机端必现)出现整个页面下移动40像素。当蓝条消失时,显示顶部控件到手机顶部比期望的高20像素,工具栏显示的状态栏背景为黑色。当显示热点蓝条时,通过View UI Hierarchy,观察立体图层发现该种异常情况下,页面的全屏幕布局分了3个阶梯,每一个阶梯相差20像素;当不显示热点蓝条时,通过View UI Hierarchy,观察立体图层发现该种异常情况下,页面的全屏幕布局分了2个阶梯,每一个阶梯相差20像素。

当然当应用启动前手机不显示蓝条,进入首页面显示热点蓝条时,通过View UI Hierarchy,观察立体图层发现该种情况下,页面的全屏幕布局分了2个阶梯,每一个阶梯相差20像素;当热点蓝条消失后,通过View UI Hierarchy,观察立体图层发现该种异常情况下,页面的全屏幕布局都一个高度。

通过分析上面两种情况的全屏幕图层的frame高度和起始高度数据看到第6层及以后的名称为 UILayoutContainerView图层显然出现了明显的差异,如:

进入时有蓝条,及蓝条消失第7层的打印数据:

------------[ 6] UILayoutContainerView, height:548.000000, origin.y:20.000000

------------[ 6] UILayoutContainerView, height:548.000000, origin.y:20.000000

进入时没有蓝条,蓝条显示和消失,再显示时的第7层的打印数据:

------------[ 6] UILayoutContainerView, height:568.000000, origin.y:0.000000

------------[ 6] UILayoutContainerView, height:568.000000, origin.y:0.000000

------------[ 6] UILayoutContainerView, height:568.000000, origin.y:0.000000

可以看到不同,这就是进入时有蓝条,当进入首页时顶部控件距离热点蓝条多20像素,当热点蓝条消失时显示的状态栏有20个像素的黑色区域并且首页的顶部控件到状态栏多20个像素的真正原因。

既然知道了原因,只需要判断出这这种情况,修改大于等于第6层的 UILayoutContainerView图层frame就可以了,看来还是需要修改系统底层的东西啊!应用的图层都是在第8层UINavigationTransitionView以后的图层,并且名称都不叫UILayoutContainerView。只要在加载首页面时判断出这种情况修改好就可以。可以通过遍历keyWindow的所有图层,找到异常的图层把他的frame修改成CGRectMake(0, 0, kScreenWidth, kScreenHeight)就可以。

具体需要修改的代码,在viewDidLoad调整就可以,代码如下:

- (void)dumpView:(UIView *)aView atIndent:(int)indent into:(NSMutableString *)outstring
{
    for (int i = 0; i < indent; i++) [outstring appendString:@"--"];
    [outstring appendFormat:@"[%2d] %@, height:%f, origin.y:%f\n", indent, [[aView class] description], aView.frame.size.height, aView.frame.origin.y];
    if((indent > 5) && (aView != nil) && ([[[aView class] description] isEqualToString:@"UILayoutContainerView"]))
    {
        if(aView.frame.origin.y == HOTSPOT_STATUSBAR_HEIGHT)
        {
            aView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
        }
    } 
    for (UIView *view in [aView subviews])
        [self dumpView:view atIndent:indent + 1 into:outstring];
}
// Start the tree recursion at level 0 with the root view
- (NSMutableDictionary *) displayViews: (UIView *) aView
{
    NSMutableString *outstring = [[NSMutableString alloc] init];
    UIView *keyWindowView = [[UIApplication sharedApplication] keyWindow];
    [self dumpView: aView atIndent:0 into:outstring];
    if(outstring.length != 0)
    {
        if((keyWindowView != nil) && ([keyWindowView subviews].count > 1))
        {
            UIView *UILayoutContainerView0 = [keyWindowView subviews][1];
            if(UILayoutContainerView0.frame.origin.y == HOTSPOT_STATUSBAR_HEIGHT)
            {
                [SingleObject sharedInstance].bPersonalHotspotConnected = YES;
            }
            else
            {
                [SingleObject sharedInstance].bPersonalHotspotConnected = NO;
            }
        }
        NSString *personalHotspotConnectedValue = [SingleObject sharedInstance].bPersonalHotspotConnected ? @"1":@"0";
        NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithObjectsAndKeys:outstring, @"outstring", personalHotspotConnectedValue, @"bPersonalHotspotConnected",  nil];
        return dic;
    }
    else
    {
        return nil;
    }
}
// Show the tree
- (void)logViewTreeForMainWindow
{
    NSMutableDictionary *dic = [self displayViews:[[UIApplication sharedApplication] keyWindow]];
    if((dic.count == 2) && ([dic[@"outstring"] isKindOfClass:[NSString class]]))
    {
        FLDDLogDebug(@"The view tree:\n%@", dic[@"outstring"]);
    }  
}
- (void)viewDidLoad {
    [super viewDidLoad];
    [self logViewTreeForMainWindow];
}

进入时有蓝条,及蓝条消失全部数据,第1层的数据都相同就不重复了:

[ 0] UIWindow, height:568.000000, origin.y:0.000000

–[ 1] UILayoutContainerView, height:548.000000, origin.y:20.000000

–[ 1] UILayoutContainerView, height:568.000000, origin.y:0.000000

----[ 2] UINavigationTransitionView, height:548.000000, origin.y:0.000000

----[ 2] UINavigationTransitionView, height:568.000000, origin.y:0.000000

------[ 3] UIViewControllerWrapperView, height:548.000000, origin.y:0.000000

------[ 3] UIViewControllerWrapperView, height:568.000000, origin.y:0.000000

--------[ 4] UIView, height:548.000000, origin.y:0.000000

--------[ 4] UIView, height:568.000000, origin.y:0.000000

----------[ 5] UIView, height:548.000000, origin.y:0.000000

----------[ 5] UIView, height:548.000000, origin.y:0.000000

------------[ 6] UILayoutContainerView, height:548.000000, origin.y:20.000000

------------[ 6] UILayoutContainerView, height:548.000000, origin.y:20.000000

--------------[ 7] UINavigationTransitionView, height:548.000000, origin.y:0.000000

--------------[ 7] UINavigationTransitionView, height:548.000000, origin.y:0.000000

----------------[ 8] UIViewControllerWrapperView, height:548.000000, origin.y:0.000000

----------------[ 8] UIViewControllerWrapperView, height:548.000000, origin.y:0.000000

进入时没有蓝条,蓝条显示和消失的全部数据,第1层的数据都相同就不重复了:

[ 0] UIWindow, height:568.000000, origin.y:0.000000

–[ 1] UILayoutContainerView, height:568.000000, origin.y:0.000000

–[ 1] UILayoutContainerView, height:548.000000, origin.y:20.000000

–[ 1] UILayoutContainerView, height:568.000000, origin.y:0.000000

----[ 2] UINavigationTransitionView, height:568.000000, origin.y:0.000000

----[ 2] UINavigationTransitionView, height:548.000000, origin.y:0.000000

----[ 2] UINavigationTransitionView, height:568.000000, origin.y:0.000000

------[ 3] UIViewControllerWrapperView, height:568.000000, origin.y:0.000000

------[ 3] UIViewControllerWrapperView, height:548.000000, origin.y:0.000000

------[ 3] UIViewControllerWrapperView, height:568.000000, origin.y:0.000000

--------[ 4] UIView, height:568.000000, origin.y:0.000000

--------[ 4] UIView, height:548.000000, origin.y:0.000000

--------[ 4] UIView, height:568.000000, origin.y:0.000000

----------[ 5] UIView, height:568.000000, origin.y:0.000000

----------[ 5] UIView, height:568.000000, origin.y:0.000000

----------[ 5] UIView, height:568.000000, origin.y:0.000000

------------[ 6] UILayoutContainerView, height:568.000000, origin.y:0.000000

------------[ 6] UILayoutContainerView, height:568.000000, origin.y:0.000000

------------[ 6] UILayoutContainerView, height:568.000000, origin.y:0.000000

--------------[ 7] UINavigationTransitionView, height:568.000000, origin.y:0.000000

--------------[ 7] UINavigationTransitionView, height:568.000000, origin.y:0.000000

--------------[ 7] UINavigationTransitionView, height:568.000000, origin.y:0.000000

----------------[ 8] UIViewControllerWrapperView, height:568.000000, origin.y:0.000000

----------------[ 8] UIViewControllerWrapperView, height:568.000000, origin.y:0.000000

----------------[ 8] UIViewControllerWrapperView, height:568.000000, origin.y:0.000000

------------------[ 9] UIView, height:568.000000, origin.y:0.000000

------------------[ 9] UIView, height:568.000000, origin.y:0.000000

------------------[ 9] UIView, height:568.000000, origin.y:0.000000

--------------------[10] UIImageView, height:568.000000, origin.y:0.000000

--------------------[10] UIImageView, height:568.000000, origin.y:0.000000

--------------------[10] UIImageView, height:568.000000, origin.y:0.000000

进入时有蓝条的图层层次图(异常场景):

进入时有蓝条,热点共享蓝条消失后的图层层次图(异常场景):

进入时没有有蓝条,热点共享蓝条出现后的图层层次图(正常场景):

进入时没有热点共享蓝条的图层层次图(正常场景):

进入时没有热点蓝条,当热点蓝条消失时,状态栏显示为20个像素的黑色区域,顶部控件到顶部的距离也比预期的高20个像素的截图如下(异常场景):

进入时有热点蓝条,当热点蓝条消失时,修正后的正确截图如下:

进入时有热点蓝条,修正后的正确截图如下:

下面是微信没有完美解决热点共享蓝条下压后导致顶部工具栏异常的图片。可见只是靠接收UIApplicationWillChangeStatusBarFrameNotification通知来处理它引起的状态栏异常是不完美的。我也经过很长时间的修改探索才找到这个完美解决方案。对工作执着、对产品负责的态度,极度注重细节,不断追求完美,没有绝对完美的产品,我们都是在追求路上。

注意:iOS13.51后,苹果抄袭安卓,把那个多出20像素状态栏修改为显示左上角时间背景颜色,并把时间换成对应图标一小段时间,然后图标消失,仍旧显示时间。所以苹果的热点共享(蓝色),后台定位(蓝色)和后台语音(红色)不会出现状态栏下压20像素导致底部页面20像素显示不出来的问题了。这个小功能变更不知道苹果什么时候修改的,苹果也没有给这方面的说明,我估计可能是iOS13时修改的。不过这样我们不用再适配这种页面混乱了。其实原来影响最差的不是普通页面,而是启动页面。若你的启动页面中间有有意义的图像。当热点状态栏出来时,会把中间截取20像素扔掉。

后台定位刚出现时。

热点共享工具栏出现一段时间后。

后台语音刚出现时。

目录
相关文章
|
人工智能 数据可视化 API
36.7K star!拖拽构建AI流程,这个开源LLM应用框架绝了!
`Flowise` 是一款革命性的低代码LLM应用构建工具,开发者通过可视化拖拽界面,就能快速搭建基于大语言模型的智能工作流。该项目在GitHub上线不到1年就斩获**36.7K星标**,被开发者誉为"AI时代的乐高积木"。
949 8
|
9月前
|
运维 监控 Kubernetes
Bitnami 替代品:Websoft9 如何接力单服务器多应用时代
Bitnami 曾为开源应用部署带来革命性体验,但随着 Docker 成熟与战略转向云原生,其单机多应用支持逐渐弱化。面对多应用管理分散、资源冲突、运维工具缺失等痛点,Websoft9 应运而生,提供一键部署、统一管理、智能调度等能力,全面优化单服务器多应用运维体验,成为 Bitnami 的理想继任者。
323 0
Bitnami 替代品:Websoft9 如何接力单服务器多应用时代
|
9月前
|
JSON 监控 数据可视化
揭秘淘宝 API,让天猫店铺流量来源一目了然
在竞争激烈的电商环境中,天猫商家最关心的问题之一是流量来源。本文介绍如何利用淘宝开放平台的API接口,帮助商家清晰掌握店铺流量渠道,包括直接访问、搜索、广告及社交媒体流量。通过API获取数据后,可进一步分析访问量、来源占比、跳出率等关键指标,优化营销策略,提升转化率。结合Python编程与图表工具,实现数据可视化分析,助力商家做出数据驱动决策,抢占市场先机。
775 1
|
存储 JavaScript 算法
JS垃圾回收机制有哪些?
本文介绍了JavaScript中的垃圾回收(GC)机制,包括其概念、产生原因及重要性。文章详细讲解了几种常见的垃圾回收算法,如引用计数、标记清除、标记整理和分代回收,并分析了它们的优缺点。最后总结了垃圾回收对JS开发的重要作用,强调了其在自动内存管理和性能优化中的关键地位。
754 2
JS垃圾回收机制有哪些?
|
机器学习/深度学习 分布式计算 并行计算
推荐一些机器学习系统MLSys中的值得研究的方向
MLsys不能算是一种方向,而是一种思路。比如对于system研究者来说,可以把ML作为我们开发的系统要适配的一种benchmark,就像transaction对于数据库、某种文件场景对于File System的意义一样。这样一想可做的空间就宽广多了
1843 0
|
存储 iOS开发 Perl
ios-解决报错-CocoaPods could not find compatible versions for pod “xxx“
ios-解决报错-CocoaPods could not find compatible versions for pod “xxx“
1254 2
|
JSON JavaScript 前端开发
TypeScript(十六)配置相关(tsconfig配置)
TypeScript(十六)配置相关(tsconfig配置)
892 1
关于支付宝异步通知的那些事
一、如何指定接收异步通知的地址     对于支付产生的交易,支付宝会根据原始支付API中传入的异步通知地址notify_url,通过POST请求的形式将支付结果 作为参数通知到商户系统。     以app支付为例,支付请求中notify_url参数的(如下图):      二、异步通知返回参数介绍     支付宝通过POST请求的形式将支付结果作为参数通知到商户系统。
3295 12
|
测试技术 Linux 开发工具
软件测试之【软件测试初级工程师技能点全解】
软件测试之【软件测试初级工程师技能点全解】
674 0
|
数据安全/隐私保护 Perl
批量计算地震波PGA/PGV/PGD、PSA/PSV/PSD、特征周期、卓越频率、Arias强度、特征强度、能量密度、Housner强度等30+参数
地震波格式转换、时程转换、峰值调整、规范反应谱、计算反应谱、计算持时、生成人工波、时频域转换、数据滤波、基线校正、Arias截波、傅里叶变换、耐震时程曲线、脉冲波合成与提取、三联反应谱、地震动参数、延性反应谱、地震波缩尺、功率谱密度