iPhoneX适配之Home-Indicator

前言

苹果一向特立独行,iPhoneX的横空出世更是令人咋舌,果粉自然惊喜,然而因为其刘海,iOS工程师却不得不进行苦逼的适配。关于iPhoneX的适配,网上自然非常之多,里面不乏优秀杰作。虽然也曾经独自承担了QQ阅读的iPhoneX适配工作,但是好多问题的解决之道都是相同的,本文不会再重复喋喋不休这些众所周知的适配问题,而是主要针对iPhoneX的Home-Indicator进行分析,一个似乎被遗忘到角落里的微不足道的适配点。

问题引发点

对于大多数的App应用,或许完全没有这个必要进行Home-Indicator的适配,因为这个home条时时刻刻都在提醒用户改变原有的home键使用习惯,它的存在旨在替代原有的home键
然而对于某些特殊的应用,比如阅读类的,视频类的,游戏类的,因为这些应用的核心功能都是处于一种沉浸式的环境中,此时Home-Indicator的呈现就显得格外刺目,尤其是在夜间模式下的那一道白光。
可以先体验一下(这里用的是模拟器,实际上真机上的体验要比模拟器更让人不舒服)



大概看了几款阅读软件,分别是掌阅,微信读书,百度阅读,书旗小说,基本上都没有针对这个做适配,不知道是不是iPhoneX的市场小导致。不过目前看来,Home-Indicator的适配还是很有必要的,接下来以QQ阅读为例来分析下适配过程:

白天模式初步适配

好吧,根据官方的指示,开始适配
先看一下UIViewController的头文件,iOS 11 增加了UIViewController的一个 UIHomeIndicatorAutoHidden分类来控制home键的自动隐藏

@interface UIViewController (UIHomeIndicatorAutoHidden)

// Override to return a child view controller or nil. If non-nil, that view controller's home indicator auto-hiding will be used. If nil, self is used. Whenever the return value changes, -setNeedsHomeIndicatorAutoHiddenUpdate should be called.
- (nullable UIViewController *)childViewControllerForHomeIndicatorAutoHidden API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(watchos, tvos);

// Controls the application's preferred home indicator auto-hiding when this view controller is shown.
- (BOOL)prefersHomeIndicatorAutoHidden API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(watchos, tvos);

// This should be called whenever the return values for the view controller's home indicator auto-hiding have changed.
- (void)setNeedsUpdateOfHomeIndicatorAutoHidden API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(watchos, tvos);

@end

实际上可以发现HomeIndicator的控制和状态栏的控制很相似,那么按照上述API开始适配:

  • 1、在需要隐藏的VC里面覆盖函数prefersHomeIndicatorAutoHidden
- (BOOL)prefersHomeIndicatorAutoHidden{
    return YES;
}
  • 2、在合适的地方调用setNeedsUpdateOfHomeIndicatorAutoHidden
if (@available(iOS 11.0, *)) {
    [self setNeedsUpdateOfHomeIndicatorAutoHidden];
}

够了吗?貌似上述方法还不够,没有生效,原因是:
重写控制器的此方法的这种情况只适用于此控制器没有父控制器的情形,而绝大部分的应用都是以UITabBarControllerUINavigationController作为window的根控制器,UIViewController来展示内容,这时还需要重写根控制器的childViewControllerForHomeIndicatorAutoHidden方法

这里需要注意的是:这里的根控制器并不一定就是UINavigationController或者UITabBarController,可能是接入第三方的,也有可能是自定义的,例如QQ阅读的根控制器XXXViewController就是自定义的

以下是对根控制器XXXViewControllerchildViewControllerForHomeIndicatorAutoHidden的重写

- (UIViewController*)childViewControllerForHomeIndicatorAutoHidden
{
    id<UIApplicationDelegate> * appDelegate = UIApplication sharedApplication].delegate
    UIViewController *topViewController = appDelegate.viewController.topViewController;

    // XXXViewController是我们想控制Home-Indicator的VC
    if ([topViewController isKindOfClass:[XXXViewController class]])
    {
        return topViewController;
    }

    return nil;
}

经过上述适配步骤之后,白天模式下进入阅读页,Home-Indicator将会显示,如果页面无点击操作,大概1s左右,Home-Indicator将会自动隐藏。但是阅读页一旦发生点击操作,Home-Indicator还是会再次显示,不过很快又会自动消失。

如果适配到此就结束了,这篇文章确实没有写的必要了,然而适配QQ阅读Home-Indicator遇到的坑才真正开始…

阅读页切换夜间模式

在我的理解来看,正常的夜间模式,应该是专门的一套皮肤来做,省心省力,如果您的项目工程就是这样实现的,那么也基本上不会遇到我碰到的坑。但是有的项目工程可能由于历史原因,并没有这么做,而是采取了其它方式: 在当前的Window的上面加一个不能响应事件的Window,并对其rootVC的view的透明度进行设置,故而造成一定的夜间视觉感 ,这种方式可能工作量小一些,但是却可能引发各种各样的问题,比如之前影响到页面的跳转,又比如这次它就影响到了Home-Indicator的适配。

这种夜间模式的实现如下:

- (void)enableNightMode
{
    UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0,0,kScreenWidth, kScreenHeight)];
    window.userInteractionEnabled = NO;
    window.backgroundColor = [UIColor clearColor];
    window.windowLevel = XXXLevelGlobalNightMode;
    XXXNightModeMaskViewController *vc = [[XXXNightModeMaskViewController alloc] init];
    vc.view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.35];
    window.rootViewController = vc;
    [window makeKeyAndVisible];
    self.nightModeWindow = window;
}

正是由于上面覆盖了一个window,导致每次在阅读页切换夜间模式的时候调用方法childViewControllerForHomeIndicatorAutoHidden时,每次都调到了XXXNightModeMaskViewController中,而不是前文提到的正常Window的根控制器中,这样我们之前设置的代码就无法被调用,进而导致控制失效。

解决方案就是重写XXXNightModeMaskViewControllerchildViewControllerForHomeIndicatorAutoHidden方法

以下是对XXXNightModeMaskViewController的方法childViewControllerForHomeIndicatorAutoHidden的重写

- (UIViewController*)childViewControllerForHomeIndicatorAutoHidden
{
    // 找到正常的window
    ...

    // 找到topViewController
    UIViewController *topViewController = window.rootViewController.topViewController;

    // 把想控制的VC放到这里进行判断,当然也可以放到一个白名单集合里
    if ([topViewController isKindOfClass:[XXXViewController class]])
    {
        return topViewController;
    }

    return nil;
}

夜间模式下进入阅读页

问题解决完了吗?还没有,因为在夜间模式下,从阅读页外面(比如书架)直接进入阅读页,Home-Indicator还是无法消失。

  • 1、先来看下之前的阅读页切换夜间模式的调用堆栈:

    如果在阅读页切换夜间模式,则会触发[UIWindow makeKeyAndVisiable],进而触发childViewControllerForHomeIndicatorAutoHidden。但是由于其不能响应任何事件,导致这是它唯一能触发childViewControllerForHomeIndicatorAutoHidden的机会。

  • 2、再看下白天模式下从书架直接进入阅读页的堆栈:

    目测[UINavigationController pushViewController:animated:]也可以触发childViewControllerForHomeIndicatorAutoHidden

  • 3、在夜间模式下,[UINavigationController pushViewController:animated:]也会被调用,然而却无法触发根控制器中childViewControllerForHomeIndicatorAutoHidden方法的调用,导致再次失效,猜想可能和keyWindow有关系,但是很遗憾,未能找到原因(希望有高手指点),不过这并不意味着找不到解决方案:在我们想控制的XXXViewController中,重新触发一下夜间模式就可以了
    ```objc

  • (void)viewDidLoad
    {
    [super viewDidLoad];
    if(IS_IPHONEX && !_isDayMode){
      [ThemeMgr enableNightMode];
    
    }
    }
    ```

效果

来看下适配后的效果图


总结

Home-Indicator的适配如果这样循序渐进地进行,基本上时间花销不会太久,只可惜一开始我就掉到了夜间模式的陷进里,并且未想到Home-Indicator的适配会和这种夜间模式有什么关系,导致我折腾了很久。我一度认为这个适配可能就和应用内评分一样,客户端的掌控是很弱的,并且对苹果的官方意见深信不疑:

  • The indicator will automatically adjust itself depending on what’s below it, and it’s not something you can directly control. As described in Designing for iPhone X, you generally don’t want to do anything in your app specific to the home indicator

不过实际上来看,虽然现在已经适配完了,客户端的掌控力还是很弱,目前还是有一定的缺陷:那就是只要在阅读页内发生touch行为,Home-Indicator就会进行短暂的出现,之后会缓慢消失,这应该是苹果内部的实现机制导致的,我们估计无能为力。我大概看了下适配过Home-Indicator的视频类App和游戏类App,基本上都存在这个问题。幸运的是,手势不会有太大的影响,QQ阅读页的手势翻页基本上不会影响到Home-Indicator出现,猜想这可能和手势的响应优先级有关。
最后希望以后能有更好的完善。

-------------本文结束 感谢您的阅读-------------

本文标题:iPhoneX适配之Home-Indicator

文章作者:lingyun

发布时间:2018年01月28日 - 23:01

最后更新:2018年02月09日 - 22:02

原始链接:https://tsuijunxi.github.io/2018/01/28/iPhoneX适配之Home-Indicator/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。