更新iOS11后,发现有些地方需要做适配,整理后按照优先级分为以下三类:


  1. 单纯升级iOS11后造成的变化;
  2. Xcode9 打包后造成的变化;
  3. iPhoneX的适配


一、单纯升级iOS11后造成的变化

1. 升级后,发现某个拥有tableView的界面错乱,组间距和contentInset错乱,因为iOS11中 UIViewController 的 automaticallyAdjustsScrollViewInsets 属性被废弃了,因此当tableView超出安全区域时,系统自动会调整SafeAreaInsets值,进而影响adjustedContentInset值
// 有些界面以下使用代理方法来设置,发现并没有生效
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
// 这样的原理是因为之前只是实现了高度的代理方法,却没有实现View的代理方法,iOS10及以前这么写是没问题的,iOS11开启了行高估算机制引起的bug,因此有以下几种解决方法:
// 解决方法一:添加实现View的代理方法,只有实现下面两个方法,方法 (CGFloat)tableView: heightForFooterInSection: 才会生效
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
return nil;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return nil;
}
// 解决方法二:直接使用tableView属性进行设置,修复该UI错乱
self.tableView.sectionHeaderHeight = 0;
self.tableView.sectionFooterHeight = 5;
[_optionTableView setContentInset:UIEdgeInsetsMake(-35, 0, 0, 0)];
// 解决方法三:添加以下代码关闭估算行高
self.tableView.estimatedRowHeight = 0;
self.tableView.estimatedSectionHeaderHeight = 0;
self.tableView.estimatedSectionFooterHeight = 0;

2. 如果使用了Masonry 进行布局,就要适配safeArea
if ([UIDevice currentDevice].systemVersion.floatValue >= 11.0) {
make.edges.equalTo(self.view.safeAreaInsets);
} else {
make.edges.equalTo(self.view);
}

3. 对于IM的发送原图功能,iOS11启动全新的HEIC 格式的图片,iPhone7以上设备+iOS11排出的live照片是".heic"格式图片,同一张live格式的图片,iOS10发送就没问题(转成了jpg),iOS11就不行

  • 微信的处理方式是一比一转化成 jpg 格式
  • QQ和钉钉的处理方式是直接压缩,即使是原图也压缩为非原图
  • 微信的逼格太高,没找到现成的方法,我采用的是QQ 的方案


二、使用Xcode9 编译后发现的问题

1. 发现“fastSocket”第三方报错,具体原因是缺少C99的头文件,引入“#include <sys/time.h>”即可

详解iOS11、iPhone X、Xcode9 适配指南_ide

2. 导航栏的新特性
  • 原生的搜索栏样式发生改变

详解iOS11、iPhone X、Xcode9 适配指南_ide_02

右边为iOS11样式,搜索区域高度变大,字体变大

查看 API 后发现,iOS11后将 searchController 赋值给了 NavigationItem,通过属性 hidesSearchBarWhenScrolling 可以控制搜索栏是否在滑动的时候进行隐藏和显示

// A view controller that will be shown inside of a navigation controller can assign a UISearchController to this property to display the search controller’s search bar in its containing navigation controller’s navigation bar.
@property (nonatomic, retain, nullable) UISearchController *searchController API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos);
@property (nonatomic) BOOL hidesSearchBarWhenScrolling API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos);



另外,UINavigationBar 新增属性 BOOL值 prefersLargeTitles 来实现下面的效果,并可以通过 largeTitleTextAttributes 来设置大标题的文本样式。设置大标题之后,导航栏的高度就会由之前的64pt变成 96pt,如果项目中有直接写死的高度或者隐藏导航栏之类的操作,就需要适配一下


详解iOS11、iPhone X、Xcode9 适配指南_ide_03


有个界面使用到了导航栏按钮相关的frame,也发生了UI错乱,查看UI层级关系后发现,iOS11以前是直接把按钮加到了UINavigationBar上面,而iOS11则是先将按钮加到了_UITAMICAdaptorView,再加到_UIButtonBarStackView_UINavigationBarContentView,接着才是UINavigationBar。因此如果需要获取导航栏按钮 frame 或者 superView,这里需要专门做下适配


详解iOS11、iPhone X、Xcode9 适配指南_iphone_04

iOS10及以下版本导航栏按钮层级关系图

详解iOS11、iPhone X、Xcode9 适配指南_iphone_05

iOS11导航栏按钮层级关系图


三、iPhone X的适配

下载完Xcode9之后,第一件事自然是在 iPhone X(模拟器)上过把瘾,然后编译后就发现报错了
由于iPhone X的状态栏是和其他版本手机差异比较大的,因此api 变化也比较大
先后做了以下适配

适配点一:项目中使用状态栏中图标判断当前网络的具体状态

详解iOS11、iPhone X、Xcode9 适配指南_ios_06

出错代码

打印的 Log 报出以下错误: Trapped uncaught exception 'NSUnknownKeyException', reason: '[<UIStatusBar_Modern 0x7fcdb0805770> valueForUndefinedKey:]: this class is not key value coding-compliant for the key foregroundView.'

详解iOS11、iPhone X、Xcode9 适配指南_iphone_07

iPhone X

详解iOS11、iPhone X、Xcode9 适配指南_ios_08

其他手机

使用 runtime 打印其所有属性,发现以下差异

// 测试代码
#import <objc/runtime.h>
NSMutableString *resultStr = [NSMutableString string];
//获取指定类的Ivar列表及Ivar个数
unsigned int count = 0;
Ivar *member = class_copyIvarList([[application valueForKeyPath:@"_statusBar"] class], &count);
for(int i = 0; i < count; i++){
Ivar var = member[i];
//获取Ivar的名称
const char *memberAddress = ivar_getName(var);
//获取Ivar的类型
const char *memberType = ivar_getTypeEncoding(var);
NSString *str = [NSString stringWithFormat:@"key = %s type = %s \n",memberAddress,memberType];
[resultStr appendString:str];
}
NSLog(@"%@", resultStr);
// 其他版本的手机
key = _inProcessProvider type = @"<UIStatusBarStateProvider>"
key = _showsForeground type = B
key = _backgroundView type = @"UIStatusBarBackgroundView"
key = _doubleHeightLabel type = @"UILabel"
key = _doubleHeightLabelContainer type = @"UIView"
key = _currentDoubleHeightText type = @"NSString"
key = _currentRawData type = {超长。。}
key = _interruptedAnimationCompositeViews type = @"NSMutableArray"
key = _newStyleBackgroundView type = @"UIStatusBarBackgroundView"
key = _newStyleForegroundView type = @"UIStatusBarForegroundView"
key = _slidingStatusBar type = @"UIStatusBar"
key = _styleAttributes type = @"UIStatusBarStyleAttributes"
key = _waitingOnCallbackAfterChangingStyleOverridesLocally type = B
key = _suppressGlow type = B
key = _translucentBackgroundAlpha type = d
key = _showOnlyCenterItems type = B
key = _foregroundViewShouldIgnoreStatusBarDataDuringAnimation type = B
key = _tintColor type = @"UIColor"
key = _lastUsedBackgroundColor type = @"UIColor"
key = _nextTintTransition type = @"UIStatusBarStyleAnimationParameters"
key = _overrideHeight type = @"NSNumber"
key = _disableRasterizationReasons type = @"NSMutableSet"
key = _timeHidden type = B
key = _statusBarWindow type = @"UIStatusBarWindow"
// iPhone X
key = _statusBar ; type = @"_UIStatusBar"
// 因此可见iPhone X的状态栏是多嵌套了一层,多取一次即可,最终适配代码为:
NSArray *children;
// 不能用 [[self deviceVersion] isEqualToString:@"iPhone X"] 来判断,因为iPhone X 的模拟器不会返回 iPhone X
if ([[application valueForKeyPath:@"_statusBar"] isKindOfClass:NSClassFromString(@"UIStatusBar_Modern")]) {
children = [[[[application valueForKeyPath:@"_statusBar"] valueForKeyPath:@"_statusBar"] valueForKeyPath:@"foregroundView"] subviews];
} else {
children = [[[application valueForKeyPath:@"_statusBar"] valueForKeyPath:@"foregroundView"] subviews];
}

适配点二:解决这个问题后项目跑起来发现,整个app界面上下各空出大概40pt的高度

详解iOS11、iPhone X、Xcode9 适配指南_iphone_09

经常从 Github 上下载项目把玩的老司机们都知道,有些老项目在模拟器上跑起来之后也会只有 iPhone 4(320480)的布局空间,造成这个的原因是启动图使用 Launch Images Source 设置的时候没有勾选并设置对应的图片(11252436),解决方法如下

详解iOS11、iPhone X、Xcode9 适配指南_ios_10

但是即使按照上面的操作进行之后,会发现底部 UITabBar 依旧是高出一些高度,查看层级关系后发现,同样是由于安全区的原因,UITabBar 高度由49pt变成了83pt,因此这里也要对iPhone X 及其模拟器进行适配


适配点三:iPhone X 只有 faceID,没有touchID,如果in的应用有使用到 touchID 解锁的地方,这里要根据机型进行相应的适配

适配点四:之前有偷懒的直接使用20替代状态栏高度,这些坑都要通过重新获取状态栏高度
CGRectGetHeight([UIApplication sharedApplication].statusBarFrame)

适配点五:然而iPhone X更大的坑是屏幕的适配

首先看下屏幕尺寸


详解iOS11、iPhone X、Xcode9 适配指南_iphone_11


这张图反映出不少信息:


  • iPhone X的宽度虽然和7是一样的,但是高度多出145pt
  • 使用三倍图是重点,而且一般认为肉眼所能所能识别的最高的屏幕密度是300ppi,iPhone X已达到458ppi(查证发现三星galaxy系列的屏幕密度是522ppi)

在设计方面,苹果官方文档human-interface-guidelines有明确要求,下面结合图例进行说明:

详解iOS11、iPhone X、Xcode9 适配指南_ide_12

展示出来的设计布局要求填满整个屏幕

详解iOS11、iPhone X、Xcode9 适配指南_ios_13

填满的同时要注意控件不要被大圆角和传感器部分所遮挡

详解iOS11、iPhone X、Xcode9 适配指南_ide_14

安全区域以外的部分不允许有任何与用户交互的控件


上面这张图内含信息略多




头部导航栏不予许进行用户交互的,意味着下面这两种情况 Apple 官方是不允许的


详解iOS11、iPhone X、Xcode9 适配指南_ios_15

详解iOS11、iPhone X、Xcode9 适配指南_iphone_16


底部虚拟区是替代了传统home键,高度为34pt,通过上滑可呼起多任务管理,考虑到手势冲突,这部分也是不允许有任何可交互的控件,但是设计的背景图要覆盖到非安全区域

状态栏在非安全区域,文档中也提到,除非可以通过隐藏状态栏给用户带来额外的价值,否则最好把状态栏还给用户

重复使用现有图片时,注意长宽比差异。iPhone X 与常规 iPhone 的屏幕长宽比不同,因此,全屏的 4.7 寸屏图像在 iPhone X 上会出现裁切或适配宽度显示。所以,这部分的视图需要根据设备做出适配。


详解iOS11、iPhone X、Xcode9 适配指南_ios_17

注意类似占位图的适配


适配点六:设备信息
if ([deviceString isEqualToString:@"iPhone10,1"])   return @"iPhone 8";
if ([deviceString isEqualToString:@"iPhone10,4"]) return @"iPhone 8";
if ([deviceString isEqualToString:@"iPhone10,2"]) return @"iPhone 8 Plus";
if ([deviceString isEqualToString:@"iPhone10,5"]) return @"iPhone 8 Plus";
if ([deviceString isEqualToString:@"iPhone10,3"]) return @"iPhone X";
if ([deviceString isEqualToString:@"iPhone10,6"]) return @"iPhone X";

更多新设备信息详见Github-iOS-getClientInfo







详解iOS11、iPhone X、Xcode9 适配指南_ios_18