适配iPhoneX & iOS11

一、Screen Size

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

#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。

二、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.

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

竖屏

横屏

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

#define STATUS_HEIGHT   (IS_IPHONE_X?44:20)

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

#define BOTTOM_SAFEAREA_HEIGHT (IS_IPHONE_X? 34 : 0)

如果你同时也用了自定义的UITabBar那么就需要修改TABBAR_HEIGHT的宏定义为:

#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

三、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 = YES;
    }

四、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宽度相同

五、Tips

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

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

2、重新布局

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

3、全屏显示

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

4、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"
}

5、定位

在 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 ?
(这是一个带简体中文字幕的视频,我并没有看!!!)

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

推荐阅读更多精彩内容

  • 一、NavigationBar UIBarItem UIBarItem在iOS11在中新增landscapeIma...
    灰s阅读 3,784评论 5 11
  • 前言 苹果WWDC开发者大会上,终于发布了大家期待已久的iOS 11,有些新特性功能确实出人意料。不过大的方面苹果...
    Mr_Say_Yes阅读 3,291评论 6 15
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • iOS 11 为整个生态系统的 UI 元素带来了一种更加大胆、动态的新风格。 本文介绍iOS11中在UI方面做了哪...
    阿凡提说AI阅读 586评论 0 1
  • 我跟周小诚分手了,我们认识十一年,谈了9年的恋爱,最终还是没有熬到一起走进婚姻的这座坟墓。 初一开学的时候,他和我...
    yamxiashoihua阅读 167评论 0 0