触手iPhoneX适配实战

2,324 阅读7分钟

题外话

网上适配的帖子特别多,大多数都是官方教程的中文版。这篇文章主要记录触手TVAPP在适配中遇到的问题。
这是一次公司内部技术分享会的内容,内容共分为三个部分:

  • Xcode9新特性
  • iOS 11 适配
  • iPhone X适配

这是第三部分。这里我不在重复前两部分的内容,用到的知识点会直接使用,比如safeAreaInsets。今天的适配默认已经适配iOS 11

第一部分:Xcode9新特性
第二部分:iOS 11 适配
言归正传

简单介绍 iPhone X

ScreenSize

iPhone X具有超视网膜屏。逻辑分辨率为375pt X 812pt。屏幕尺寸5.8英寸,分辨率为1125 x 2436。四个角有圆角、顶部有齐刘海(传感器槽)、底部有HomeIndicator
StatusBar导航栏高度44pttabBar高度为83pt.

ScreenSize
ScreenSize

界面呈现最佳实践

  • 保持页面元素在安全区域内。
  • 布局元素不被圆角切割。
  • 布局元素不被齐刘海遮挡。
  • 布局元素不占用statusBarstatusBar部分有供系统使用的边缘手势。
  • 有交互的控件不被HomeIndicator重叠。

如下图

超过安全区域
超过安全区域

statusBar占用
statusBar占用

与HomeIndicator重叠
与HomeIndicator重叠

statusBar手势
statusBar手势

横屏布局
横屏布局

开始适配工作

先看看未适配前运行的结果

  1. 启动后,没有充满屏幕,上下部分有黑边。
  2. 中间的 +号按钮下沉。

    首页
    首页

  3. “我的”页面内容下沉。
    这个问题属于iOS 11适配问题,上一篇我们讲过,这里不赘述。

    我的页面
    我的页面

先把第一个问题解决了。因为iPhone X的分辨率问题,APP在启动的时候,没有相应的图片资源。解决这个问题只需对LaunchImage新增1125 x 2346启动屏图片。或者启动项从Launch Images Source->asset变更为LaunchScreenFile。指定为LaunchScreenxib或者.storyboard

运行再看效果:

首页重叠
首页重叠

发现问题:

  1. 顶部已经与statusBar重叠了。
    尝试点击,按钮上半部分不响应事件。statusBar的View在顶部,事件被statusBar截获了。这种方式也不符合UI规范。
  2. 其他页面基本都是这样的问题。
  3. 中间按钮在push到其他页面回来后,偶尔会往下掉。

在上一篇我们讲了一般使用IUNavigationBar的三种方式。触手使用的是第三种--完全自定义的view。因为触手TV的界面风格里,很多页面都是首页这个样式的,顶部是tab样式的,页面是联动的。所以我们有专门的一个控件来封装这个风格的页面。所以很久以前的工程里为了方便,直接弃用了系统的NavigationBar
做出修改之前,我们先来看看整个工程的view层级关系:

原来的APP结构
原来的APP结构

从结构中可以看出,整个APP里共用同一个NavigationController,这种做饭,有缺点也有优点,优点就是针对未适配之前的页面,隐藏导航栏,在源头就可以直接设置。在需要push的地方,除极个别被modal出来的Controller,直接可用self.navigationController获得导航属性。缺点就是不能针对定制页面控制导航栏的显示与影藏。可能你会想在viewWillAppearViewWillDisappear里去控制隐藏和显示。考虑到多个视图控制器的执行时序先后对导航栏进行设置,结果就不一定是我们想要的了。
为了更好的使用系统导航栏,又能离散的控制导航栏的隐藏,我们将APP的结构改为这样。

修改后的APP结构
修改后的APP结构

显而易见这样的结构更合理一些。我个人比较推荐使用底部tab形式的UI,都使用这样的结构。

准备工作做完了,开始适配导航栏。
在原来的MainViewController:UITabBarcontroller里有这么一段代码

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.navigationController setNavigationBarHidden:YES animated:YES];
}

这段代码就是在源头把整个APP里的导航栏隐藏的关键代码。现在可以直接删掉。
在基类的ViewControoler里,新增bool属性hiddenNavigationBar。在需要隐藏导航栏的页面,设置为true
NavigationController的基类里实现UINavigationControllerDelegate方法,根据控制器的hiddenNavigationBar属性,是否隐藏导航栏。不建议在viewWillAppearviewWillDisappear设置,这种方式在视觉上的表现不是太好,也不怎么优雅。

- (void)navigationController:(UINavigationController*)navigationController willShowViewController:(UIViewController*)viewController animated:(BOOL)animated {

    if([viewController isKindOfClass:[CSViewController class]]){
        CSViewController *vc = (CSViewController *)viewController;
        if (vc.navigationBarHidden) {
            [navigationController setNavigationBarHidden:YES animated:YES];
        } else {
            if (navigationController.navigationBarHidden) {
                [navigationController setNavigationBarHidden:NO animated:YES];
            }
        }
    }
}

我们决定使用系统的导航栏,主要原因是为了减少工作量。在原来的页面里,每个xib页面维护了一个自定义的导航栏,这次改动,每个xib文件都需要改一遍,为了一劳永逸的解决这个问题。我们觉得直接向原生控件靠拢,把适配的工作交给系统来完成。其实工作量也没多少,把每个页面的导航栏删掉就可以了。运行后效果如下:

导航示例
导航示例

针对于上面提出的中间按钮下沉问题。我们先看看iPhone X上tabBar的View层级关系。

tabBar
tabBar

高度是83,下面预留了35给虚拟home键。所以原先的直接添加到UITabBarController,设置frame就有了偏差,为了布局,使用了一个空的TabBarItem作为占位用。

buttonCenter = [[UIButton alloc] initWithFrame:CGRectMake(kScreenWidth / 2 - 30, kScreenHeight - 60 - offSety, 60, 60)];
    [buttonCenter setImage:[UIImage imageNamed:@"ic_main_center.png"] forState:UIControlStateNormal];
    [buttonCenter setImage:[UIImage imageNamed:@"ic_main_center_p.png"] forState:UIControlStateHighlighted];
    [buttonCenter addTarget:self action:@selector(clickedButtonCenter) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:buttonCenter];

解决这个问题,可以在原来的基础上针对iPhone X适配frame。我们没有这么做,而是选择继承UITabbar,重写layoutSubviews。顺便把很多运营需要的逻辑红点也剥离到这里。核心代码如下

- (void)layoutSubviews {
    [super layoutSubviews];
    Class class = NSClassFromString(@"UITabBarButton");
    CGFloat width = self.width / BarItemCount;
    for (UIView *view in self.subviews) {

    //**
    plusButton 就是需要中间的按钮
    */

        if ([view isEqual:self.plusButton]) {
            view.frame = CGRectMake(0, 0, width, width);
            view.center = CGPointMake(self.width / 2,BarItemCenterY);
        } else if ([view isKindOfClass:class]){
            //如果是系统的UITabBarButton,那么就调整子控件位置,空出中间位置
            CGRect frame = view.frame;
            NSInteger index = view.origin.x / width;
            NSInteger originIndex = index;
            if (index >= (BarItemCount - 1) / 2) {
                index++;
            }
            CGFloat x = index * width;
            view.frame = CGRectMake(x, view.frame.origin.y, width, frame.size.height);
            }
        }
    }
    [self bringSubviewToFront:self.plusButton];
}

在事件响应链里截获点击事件,对超出tabBar的部分支持事件的响应

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    //这一个判断是关键,不判断的话push到其他页面,点击发布按钮的位置也是会有反应的,这样就不好了
    //self.isHidden == NO 说明当前页面是有tabbar的,那么肯定是在导航控制器的根控制器页面
    //在导航控制器根控制器页面,那么我们就需要判断手指点击的位置是否在发布按钮身上
    //是的话让发布按钮自己处理点击事件,不是的话让系统去处理点击事件就可以了
    if (self.isHidden == NO) {
        //将当前tabbar的触摸点转换坐标系,转换到发布按钮的身上,生成一个新的点
        CGPoint newP = [self convertPoint:point toView:self.plusButton];
        CGPoint extendPoint = [self convertPoint:point toView:self.extendView];
        //判断如果这个新的点是在发布按钮身上,那么处理点击事件最合适的view就是发布按钮
        if ( [self.plusButton pointInside:newP withEvent:event]) {
            return self.plusButton;
        } else {//如果点不在发布按钮身上,直接让系统处理就可以了
            return [super hitTest:point withEvent:event];
        }
    }
    else {//tabbar隐藏了,那么说明已经push到其他的页面了,这个时候还是让系统去判断最合适的view处理就好了
        return [super hitTest:point withEvent:event];
    }
}

目前为止,我们已经解决了上面提出来的问题。
在看看其他页面,发现有几个问题。
动态详情评论框与home键区域重叠了。

动态详情评论框
动态详情评论框

直播页面的弹幕框也是这个问题。

直播页面横屏下按钮排布问题。
直播横屏
直播横屏

这些只需要基本都是UED的范畴了,对特定的约束进行调整,使其不遮挡home键。
有了第一部分:Xcode9新特性的介绍和第二部分:iOS 11 适配。iPhone X的适配就简单多了。

使用safeAreaInsets

通篇我们都没有用到safeAreaInsets来进行适配,是应为在IB界面里,safeAreaInsets支持的最低版本为iOS 9.0,我们还需要兼容之前的版本。所以这里没有介绍这种方法。
如果需要使用safeAreaInsets,那么在IB文件打开右侧第一个菜单项showfileinspector,勾选User Safe Area Layout Guides

user safe area
user safe area

在设置约束时,请选择safe area作为secondItem。这样就可以很好的适配所有机型,保证不同机型下,都能做到官方推荐的布局规范了。
safe area layout
safe area layout