适配iPhoneX & iOS11

1、Screen Size

iPhoneX 的屏幕尺寸为 375pt × 812pt @3x,像素为 1125px × 2436px。能够经过判断屏幕的高度来判断设备是不是 iPhoneX,能够在全局宏定义中添加判断设备的宏定义(横竖屏通用):ios

#define IS_IPHONE_X (( fabs((double)[[UIScreen mainScreen] bounds].size.height - (double)812) < DBL_EPSILON ) || (fabs((double)[[UIScreen mainScreen] bounds].size.width - (double)812) < DBL_EPSILON )) 复制代码

若是在 iPhoneX模拟器运行现有 app,出现上下屏幕没填充满的状况时,说明 app 没有适合 iPhoneX 尺寸的启动图,所以,须要添加一张 1125px × 2436px(@3x)的启动图,或者在项目中添加 LaunchScreen.xib,而后在项目的 target 中,设置启动 Launch Screen File 为 LaunchScreen.xib。json

2、safe Area

官方指出:安全

When designing for iPhone X, you must ensure that layouts fill the screen and aren't obscured by the device's rounded corners, sensor housing, or the indicator for accessing the Home screen.bash

当咱们在设计 iPhoneX app 的时候,必须确保布局充满屏幕,而且布局不会被设备的圆角、传感器外壳或者用于访问主屏幕的指示灯遮挡住。所以,苹果提出了safe area(安全区)的概念,就是上述可能遮挡界面的区域之外的区域被定义为安全区。app

竖屏
横屏

为了尽量的使布局和手势等不被圆角和传感器遮挡,竖屏状况下,苹果官方建议的安全区大小为上图(竖屏),指定的状态栏高度为 44pt,下方指示灯处的高度为 34pt;横屏状况下为上图(横屏)所示,上下安全边距分别为 0pt/21pt,左右安全边距为 44pt/34pt,若是使用了UINavigationBarUITabBar,安全区的上边缘会变成导航栏下边缘的位置,若是是自定义的navigationBar,而且还继承于UIView,就须要手动修改状态栏的高度,在咱们的项目中,状态栏的高度是用的全局宏定义,所以,修改状态栏高度的宏定义为:less

#define STATUS_HEIGHT   (IS_IPHONE_X?44:20)
复制代码

增长安全区域下面的区域的高度宏定义为:iphone

#define BOTTOM_SAFEAREA_HEIGHT (IS_IPHONE_X? 34 : 0)
复制代码

若是你同时也用了自定义的UITabBar那么就须要修改TABBAR_HEIGHT的宏定义为:ide

#define TABBAR_HEIGHT   (IS_IPHONE_X? (49 + 34) : 49)
复制代码

当须要将整个界面最下方的控件上移或者改变中间滚动视图的高度的时候,使用BOTTOM_SAFEAREA_HEIGHT这个宏,方便后期的统一维护。由于基本上每个界面都须要下方留白,所以在BaseViewController添加属性:布局

@property (nonatomic, strong) UIView *areaBelowSafeArea;动画

而且统一添加到view上:

#warning 背景色待定
if (IS_IPHONE_X) {
   self.areaBelowSafeArea = [[UIView alloc] initWithFrame:CGRectMake(0, SCREEN_HEIGHT - BOTTOM_SAFEAREA_HEIGHT, SCREEN_WIDTH, BOTTOM_SAFEAREA_HEIGHT)];
   // 尽可能使用约束布局
   self.areaBelowSafeArea.backgroundColor = DefaultTabBarBackgroundColor;
   [self.view addSubview:self.areaBelowSafeArea];
 }
复制代码

也能够在特定的viewController中,自定义它的样式。

self.areaBelowSafeArea.backgroundColor = XXX;
复制代码

更详细:Designing for iPhone X

3、UIScrollView及其子类

在 iOS11 中,决定滚动视图的内容和边缘距离的属性改成adjustedContentInset,而不是原来的contentInsets,在 iOS11 以前,UIViewController有一个automaticallyAdjustsScrollViewInsets属性,而且默认值为YES,这个属性的做用为,当scrollView为控制器根视图的最上层子视图时,若是这个控制器被嵌入到UINavigationControllerUITabBarController中,那么,它的contentInsets会自动设置为(64,0,49,0);这个属性会使滚动视图中的内容不被导航栏和tabBar遮挡。

iOS11 使用了safeAreaInsets的新属性,这个属性的做用就是规定了视图的安全区的四个边到屏幕的四个边的距离,例如在 iPhoneX 上,若是没使用或者隐藏了UINavigationBar,则safeAreaInsets = (44,0,0,34),若是既使用了UINavigationBar,又使用了UITabBar,则safeAreaInset = (88,0,0,34+49)。也可使用additionalSafeAreaInsets属性来为系统默认的safeAreaInsets添加 insets,好比,safeAreaInsets = (44,0,0,34),设置additionalSafeAreaInsets = UIEdgeInsetsMake(-44, 0, 0, 0);,那么实际上的安全区域到屏幕边缘的insets为(0,0,0,34)

adjustedContentInset属性的值的肯定由 iOS11 API 提供的新的枚举变量contentInsetAdjustmentBehavior决定。这个属性的类型定义为:

typedef NS_ENUM(NSInteger, UIScrollViewContentInsetAdjustmentBehavior) {
    UIScrollViewContentInsetAdjustmentAutomatic, // Similar to .scrollableAxes, but for backward compatibility will also adjust the top & bottom contentInset when the scroll view is owned by a view controller with automaticallyAdjustsScrollViewInsets = YES inside a navigation controller, regardless of whether the scroll view is scrollable
    UIScrollViewContentInsetAdjustmentScrollableAxes, // Edges for scrollable axes are adjusted (i.e., contentSize.width/height > frame.size.width/height or alwaysBounceHorizontal/Vertical = YES)
    UIScrollViewContentInsetAdjustmentNever, // contentInset is not adjusted
    UIScrollViewContentInsetAdjustmentAlways, // contentInset is always adjusted by the scroll view's safeAreaInsets
} API_AVAILABLE(ios(11.0),tvos(11.0));
复制代码

四个枚举值的意义分别为:

UIScrollViewContentInsetAdjustmentAutomatic

Content is always adjusted vertically when the scroll view is the content view of a view controller that is currently displayed by a navigation or tab bar controller. If the scroll view is horizontally scrollable, the horizontal content offset is also adjusted when there are nonzero safe area insets.

当滚动视图的父视图所在的控制器嵌入导航控制器和标签控制器的时候,滚动视图的内容总会调整垂直方向上的 insets,若是滚动视图容许水平方向上可滚动,则当水平方向上的安全区 insets 不为 0 的时候,也会调整水平方向上的 insets。即:adjustedContentInset = safeAreaInsets + contentInsets,其中contentInsets为咱们设置的滚动视图的contentInsets,下同。以下代码,

self.scroll.contentInset = UIEdgeInsetsMake(100, 0, -34, 10);
if (@available(iOS 11.0, *)) {
    self.scroll.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAutomatic;
}else {
    self.automaticallyAdjustsScrollViewInsets = YES;
}
复制代码

添加导航栏的状况下,在 iPhoneX 设备上运行打印的 log 为:

2017-10-20 11:48:56.664048+0800 TestIphoneX[81880:2541000] contentInset:{100, 0, -34, 10}
2017-10-20 11:48:56.664916+0800 TestIphoneX[81880:2541000] adjustedContentInset:{188, 0, 0, 10}
2017-10-20 11:48:56.665220+0800 TestIphoneX[81880:2541000] safeAreaInset:{88, 0, 34, 0}
复制代码

UIScrollViewContentInsetAdjustmentScrollableAxes

The top and bottom insets include the safe area inset values when the vertical content size is greater than the height of the scroll view itself. The top and bottom insets are also adjusted when the alwaysBounceVertical property is YES. Similarly, the left and right insets include the safe area insets when the horizontal content size is greater than the width of the scroll view.

adjustedContentInset = safeAreaInsets + contentInsets,它的成立依赖于滚动轴,当垂直方向上的contentSize大于滚动视图的高度时,那么垂直方向上的 insets 就由safeAreaInsets + contentInsets决定,水平方向上同理。

UIScrollViewContentInsetAdjustmentNever

Do not adjust the scroll view insets.

顾名思义,adjustedContentInset = contentInsets

UIScrollViewContentInsetAdjustmentAlways

Always include the safe area insets in the content adjustment.

顾名思义,adjustedContentInset = safeAreaInsets + contentInsets

因为咱们的 APP 没用系统的导航控制器,可是咱们用了系统的标签控制器,因此在项目中会存在 iOS11 下滚动视图的位置不对的状况,那么就多是由于它的adjustedContentInset = safeAreaInsets + contentInsets形成的,能够这样解决:

if (@available(iOS 11.0, *)) {
    self.scroll.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}else {
    self.automaticallyAdjustsScrollViewInsets = NO;
}
复制代码

4、UITableView sectionHeader 和 sectionFooter高度问题

iOS11 中 UITableView的 sectionHeader 和 sectionFooter 也启用了 self-sizing,即经过估算的高度乘以个数来肯定tableViewcontenSize的估算值,而后随着滚动展现 section 和 cell 的过程当中更新它的contenSize,iOS11 以前只有 cell 是采用的这个机制,iOS11中 sectionHeader 和 sectionFooter 也采用了这个机制,而且,若是只实现了

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
复制代码

没实现

- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;
复制代码

那么系统就会直接采用估算的高度,而不是heightForHeaderInSection方法中设置的高度,也就是此时的sectionHeight

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section
复制代码

方法,或者estimatedSectionHeaderHeight设置的估算高度,若是没有设置估算高度,则系统默认为UITableViewAutomaticDimension

因此必须同时实现heightForHeaderInSectionviewForHeaderInSection方法,能够返回[UIView new],可是不能不实现。或者只实现heightForHeaderInSection方法,而且设置estimatedSectionHeaderHeight为 0 来关闭估算机制。

注:若是在 iOS11 中,使用了 self-sizing cell,而且使用了上拉加载更多,而且使用了高度自适应的方式计算 cell 的高度,那么上拉加载更多的时候会发现 tableView 会跳动一下或者滚动一段距离,什么缘由呢,这里解释一下多是因为:假如一个列表有10个 cell,你设置的估算高度是 80,那么整个列表的估算高度为10 * 80 = 800,可是实际高度不是 800,假如是 1000,那么当滚动到最下方的时候,此时的contentOffSet = 1000,而后上拉再加载 10条数据,此时会调用- (void)reloadData;方法,此时,列表的高度仍然会从新使用估算高度计算,80 * 20 = 1600,而contentOffSet = 1000,这个位置已经不是刚才的第 10条数据了,而是第1000 / 80= 12.5条数据了,所以会形成加载更多的时候数据衔接不上的问题。你可能须要设置estimatedRowHeight = 0来关闭它的估算高度解决这个问题,可是若是你非要开启它的估算高度来使 cell 中的约束自适应高度的话,能够经过这种方式计算高度:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static DisplayCell *cell;//‘static’将cell存储在静态存储区,这里建立的cell仅用来计算高度,所以,内存中只有一份就能够了,由于此方法会调用屡次,每次都建立的话即会耗费时间也会耗费空间。
    if (!cell) {
        cell = [[[NSBundle mainBundle] loadNibNamed:kCellNibName owner:self options:nil] lastObject];
    }
    cell.displayLab.text = self.data[indexPath.row];//给cell赋值,赋值是为了经过内容计算高度
    CGFloat height = [cell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
    return height;
}
复制代码

另外,若是 cell 中使用了多行 label 的话,注意设置它的换行宽度: self.displayLab.preferredMaxLayoutWidth = XXX;//XXX应该和你约束的label宽度相同

5、Tips

一、每一个界面中的控件的位置

若是项目中用 frame 布局的控件较多,不少控件的位置依赖于self.view的顶部和底部,因为状态栏和底部空间的调整就会形成一部分控件的位置发生变化,修改过程当中应该注意和线上 APP 比对。建议能用约束的就别用 frame,依赖上下控件的位置比依赖屏幕的边缘和宽高更好维护一些。autolayout 并不影响写动画!

二、从新布局

项目中有些控件的位置会由于响应事件、动画和数据请求等从新布局,所以应该特别注意的地方就是从新布局后控件的位置是否和线上项目一致。另外,还有一些初始化时隐藏的控件,因为某些条件发生后才展现,也要注意其布局。

三、全屏显示

全屏显示和横屏模式下的界面,注意横屏以后下方的感应器在安全区以外。

四、LaunchuImage

像素为:1125 * 2436 并在LaunchImage中的Contents.json文件中增长 JSON:

{
    "extent" : "full-screen",
    "idiom" : "iphone",
    "subtype" : "2436h",
    "filename" : "图片名字.png",
    "minimum-system-version" : "11.0",
    "orientation" : "portrait",
    "scale" : "3x"
}
复制代码

五、定位

在 iOS 11 中必须支持 When In Use 受权模式(NSLocationWhenInUseUsageDescription),在 iOS 11 中,为了不开发者只提供请求 Always 受权模式这种状况,加入此限制,若是不提供When In Use 受权模式,那么 Always 相关受权模式也没法正常使用。(就是为了打倒流氓软件的流氓强制定位)

若是要支持老版本,即 iOS 11 如下系统版本,那么建议在 info.plist 中配置全部的 Key(即便 NSLocationAlwaysUsageDescription 在 iOS 11及以上版本再也不使用):

NSLocationWhenInUseUsageDescription
NSLocationAlwaysAndWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
复制代码

NSLocationAlwaysAndWhenInUseUsageDescription为 iOS 11 中新引入的一个 Key。 参考:WWDC17: What's New in Location Technologies ? (这是一个带简体中文字幕的视频,我并无看!!!)

相关文章
相关标签/搜索