iOS11、iPhone X、Xcode9 适配指南
2017.09.23
不断完善中。。。
2017.10.02 新增 iPhone X 适配官方中文文档
更新iOS11后,发现有些地方需要做适配,整理后按照优先级分为以下三类:
- 单纯升级iOS11后造成的变化;
- Xcode9 打包后造成的变化;
- 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和钉钉的处理方式是直接压缩,即使是原图也压缩为非原图
- 最终采取的是微信的方案,使用以下代码转成jpg格式
// 0.83能保证压缩前后图片大小是一致的
// 造成不一致的原因是图片的bitmap一个是8位的,一个是16位的
imageData = UIImageJPEGRepresentation([UIImage imageWithData:imageData], 0.83);
复制代码
4. 有的页面在侧滑返回或者pop操作后,会出现页面下沉的现象,效果如下图所示
// 这是因为 UIScrollView 的 contentInsetAdjustmentBehavior 属性默认为 automatic,通过以下代码可以修复
if (@available(iOS 11.0, *)) {self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
// 当然,如果是使用 Storyboard,可以依次 Size Inspector -> Content Insets -> Set 'Never' 搞定
复制代码
进行修改之后,没有 SearchViewController 的页面是没有问题的,但是拥有searchViewController 的页面,进行搜索文本的输入会造成UI错乱,因此使用以下解决方法
- (void)viewWillAppear:(BOOL)animated {[super viewWillAppear:animated];if (@available(iOS 11.0, *)) {self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;}
}- (void)viewDidAppear:(BOOL)animated {[super viewDidAppear:animated];if (@available(iOS 11.0, *)) {self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAutomatic;}
}
复制代码
5. 另外,项目中还使用了【FDTemplateLayoutCell】这个第三方用来缓存行高,孙源大神可能近期太忙,也少有更新,但是在 iOS11 上发现近期报了一个频繁的 crash
针对这个问题的解决方法,在 issue 中找到了答案:
前往文件 "UITableView+FDTemplateLayoutCell.h" 70行
if (isSystemVersionEqualOrGreaterThen10_2) {// 将这里的 UILayoutPriorityRequired 更改为 UILayoutPriorityDefaultHigh 即可解决问题widthFenceConstraint.priority = UILayoutPriorityDefaultHigh - 1;
}
复制代码
6. UITableView 的删除操作,由于iOS11 手感的优化,出现了以下问题:
- 后来查明原因是最开始写代码的时候没有注意细节,在定义删除按钮的时候没有设置合适的类型:之前是
UITableViewRowActionStyleNormal
,改为UITableViewRowActionStyleDestructive
即可 - 原因:由于没有设置 删除 所特有的type,因此在UI展示上默认是不删除的,因此适配的是保留cell的UI,只有设置删除属性后,才能和
deleteRowsAtIndexPaths
方法保持UI上的同步
UITableViewRowAction *deleteRowAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@"删除" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {[self.dataSource removeObjectAtIndex:indexPath.row];// 刷新tableview[self.tableView beginUpdates];[self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];[self.tableView endUpdates];
}
复制代码
二、使用Xcode9 编译后发现的问题
1. 发现fastSocket
第三方报错,具体原因是缺少C99的头文件,引入#include <sys/time.h>
即可
2. 导航栏的新特性
- 原生的搜索栏样式发生改变
查看 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);// If this property is true (the default), the searchController’s search bar will hide as the user scrolls in the top view controller’s scroll view. If false, the search bar will remain visible and pinned underneath the navigation bar.
@property (nonatomic) BOOL hidesSearchBarWhenScrolling API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos);
复制代码
另外,
UINavigationBar
新增属性 BOOL值prefersLargeTitles
来实现下面的效果,并可以通过largeTitleTextAttributes
来设置大标题的文本样式。设置大标题之后,导航栏的高度就会由之前的64pt变成 96pt,如果项目中有直接写死的高度或者隐藏导航栏之类的操作,就需要适配一下有个界面使用到了导航栏按钮相关的frame,也发生了UI错乱,查看UI层级关系后发现,iOS11以前是直接把按钮加到了
UINavigationBar
上面,而iOS11则是先将按钮加到了_UITAMICAdaptorView
,再加到_UIButtonBarStackView
、_UINavigationBarContentView
,接着才是UINavigationBar
。因此如果需要获取导航栏按钮frame
或者superView
,这里需要专门做下适配
三、iPhone X的适配
下载完Xcode9之后,第一件事自然是在 iPhone X(模拟器)上过把瘾,然后编译后就发现报错了 由于iPhone X的状态栏是和其他版本手机差异比较大的,因此api 变化也比较大 先后做了以下适配
适配点一:项目中使用状态栏中图标判断当前网络的具体状态
打印的 Log 报出以下错误: Trapped uncaught exception 'NSUnknownKeyException', reason: '[<UIStatusBar_Modern 0x7fcdb0805770> valueForUndefinedKey:]: this class is not key value coding-compliant for the key foregroundView.'
使用 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 Xif ([[application valueForKeyPath:@"_statusBar"] isKindOfClass:NSClassFromString(@"UIStatusBar_Modern")]) {children = [[[[application valueForKeyPath:@"_statusBar"] valueForKeyPath:@"_statusBar"] valueForKeyPath:@"foregroundView"] subviews];} else {children = [[[application valueForKeyPath:@"_statusBar"] valueForKeyPath:@"foregroundView"] subviews];}
复制代码
警告以上处理,代码看起来是不报错了,然而!!具体看了下代码发现并不生效!因为从iPhone X取出来之后只有view层级的信息,因此采用以下方法确定2G/3G/4G,从API上目测是有效的
NSArray *typeStrings2G = @[CTRadioAccessTechnologyEdge,CTRadioAccessTechnologyGPRS,CTRadioAccessTechnologyCDMA1x];NSArray *typeStrings3G = @[CTRadioAccessTechnologyHSDPA,CTRadioAccessTechnologyWCDMA,CTRadioAccessTechnologyHSUPA,CTRadioAccessTechnologyCDMAEVDORev0,CTRadioAccessTechnologyCDMAEVDORevA,CTRadioAccessTechnologyCDMAEVDORevB,CTRadioAccessTechnologyeHRPD];NSArray *typeStrings4G = @[CTRadioAccessTechnologyLTE];// 该 API 在 iOS7 以上系统才有效if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {CTTelephonyNetworkInfo *teleInfo= [[CTTelephonyNetworkInfo alloc] init];NSString *accessString = teleInfo.currentRadioAccessTechnology;if ([typeStrings4G containsObject:accessString]) {NSLog(@"4G网络");} else if ([typeStrings3G containsObject:accessString]) {NSLog(@"3G网络");} else if ([typeStrings2G containsObject:accessString]) {NSLog(@"2G网络");} else {NSLog(@"未知网络");}} else {NSLog(@"未知网络");}
复制代码
适配点二:解决这个问题后项目跑起来发现,整个app界面上下各空出大概40pt的高度
经常从 Github 上下载项目把玩的老司机们都知道,有些老项目在模拟器上跑起来之后也会只有 iPhone 4(320480)的布局空间,造成这个的原因是启动图使用 Launch Images Source 设置的时候没有勾选并设置对应的图片(11252436),解决方法如下
但是即使按照上面的操作进行之后,会发现底部 UITabBar 依旧是高出一些高度,查看层级关系后发现,同样是由于安全区的原因,UITabBar 高度由49pt变成了83pt,因此这里也要对iPhone X 及其模拟器进行适配
适配点三:iPhone X 只有 faceID,没有touchID,如果in的应用有使用到 touchID 解锁的地方,这里要根据机型进行相应的适配
适配点四:之前有偷懒的直接使用20替代状态栏高度,这些坑都要通过重新获取状态栏高度,另外没有使用自动布局的也要默默还债了
CGRectGetHeight([UIApplication sharedApplication].statusBarFrame)
复制代码
适配点五:然而iPhone X更大的坑是屏幕的适配
首先看下屏幕尺寸
这张图反映出不少信息:
- iPhone X的宽度虽然和7是一样的,但是高度多出145pt
- 使用三倍图是重点,而且一般认为肉眼所能所能识别的最高的屏幕密度是300ppi,iPhone X已达到458ppi(查证发现三星galaxy系列的屏幕密度是522ppi)
在设计方面,苹果官方文档human-interface-guidelines有明确要求,下面结合图例进行说明:
上面这张图内含信息略多
- 头部导航栏不予许进行用户交互的,意味着下面这两种情况 Apple 官方是不允许的
- 底部虚拟区是替代了传统home键,高度为34pt,通过上滑可呼起多任务管理,考虑到手势冲突,这部分也是不允许有任何可交互的控件,但是设计的背景图要覆盖到非安全区域
- 状态栏在非安全区域,文档中也提到,除非可以通过隐藏状态栏给用户带来额外的价值,否则最好把状态栏还给用户
- 不要让 界面中的元素 干扰底部的主屏幕指示器
- 重复使用现有图片时,注意长宽比差异。iPhone X 与常规 iPhone 的屏幕长宽比不同,因此,全屏的 4.7 寸屏图像在 iPhone X 上会出现裁切或适配宽度显示。所以,这部分的视图需要根据设备做出适配。
适配点六:横屏适配
关于 safe area,使用 safeAreaLayoutGuide 和 safeAreaInset就能解决大部分问题,但是横屏下还可能会产生一些问题,需要额外适配
- 产生这个原因代码是:
[headerView.contentView setBackgroundColor:[UIColor headerFooterColor]]
,这个写法看起来没错,但是只有在 iPhone X上有问题
- 解决方法:设置backgroundView颜色
[headerView.backgroundView setBackgroundColor:[UIColor headerFooterColor]]
适配点七:设备信息
if ([deviceString isEqualToString:@"iPhone10,1"]) return @"国行(A1863)、日行(A1906)iPhone 8";
if ([deviceString isEqualToString:@"iPhone10,4"]) return @"美版(Global/A1905)iPhone 8";
if ([deviceString isEqualToString:@"iPhone10,2"]) return @"国行(A1864)、日行(A1898)iPhone 8 Plus";
if ([deviceString isEqualToString:@"iPhone10,5"]) return @"美版(Global/A1897)iPhone 8 Plus";
if ([deviceString isEqualToString:@"iPhone10,3"]) return @"国行(A1865)、日行(A1902)iPhone X";
if ([deviceString isEqualToString:@"iPhone10,6"]) return @"美版(Global/A1901)iPhone X";
复制代码
更多新设备信息详见**Github-iOS-getClientInfo**
适配点八:如果是业务需求需要隐藏底部Indicator
// 在VC里面重写下面这个方法即可
- (BOOL)prefersHomeIndicatorAutoHidden{return YES;
}
复制代码
- 史上第二走心的 iOS11-Drag & Drop 教程
- 2017.10.02补充:iPhone X 中文官方适配文档
- iOS11/iPhone X 适配简单,但你的Apple思维适配做好了么?
iOS11、iPhone X、Xcode9 适配指南相关推荐
- iphone X 屏幕适配
1.前言 就在今天(2017年09月13日 )凌晨,苹果发布了带刘海的 iPhone X,这个屏幕需要 iOS 开发者做新的适配,会后苹果发布了 iPhone X 的适配指南,我们将它翻译出来供大家参 ...
- iOS11 iPhone X 适配指南
苹果WWDC开发者大会上,终于发布了大家期待已久的iOS 11,有些新特性功能确实出人意料.不过大的方面苹果貌似也就 AR 和 GM 机器学习了,9月13日凌晨1点,苹果开了新品发布会,相信大家都已经 ...
- iOS11 和 iPhone X 的适配
一.首页进行iOS11系统的适配. 1.我手中的 iPhone6s更新到了11,发现UITableView 和 UICollectionView 的 页面页脚变宽了.原因是因为 iOS11 默认开启了 ...
- 移动端适配指南!聊聊安卓折叠屏给交互设计和开发带来的变化
移动端的时代前端同学面临着各种适配的处理.刘海机的出现,前端需要考虑刘海机适配.如今,随着折叠屏手机的面世,前端同学接着又要处理折叠屏幕的适配.本文除了介绍折叠机给我们带来的变化之外,还提出了响应式设 ...
- 刘海I关于iPhone X 的适配
关于iPhone X 的适配 为了实现更为安全的面部识别,iPhone X正面的全面屏上方有一条刘海,上面集成了红外(深度)摄像头.近距离传感器.环境光传感器.左/上扬声器.麦克风.前置摄像头和一个名 ...
- iOS开发 关于iPhone X 的适配
友情链接:XCODE 9:最新功能详尽介绍 1.屏幕尺寸相关变化 高度增加了145pt,变成812pt. 屏幕圆角显示,注意至少留10pt边距. 状态栏高度由20pt变成44pt,留意这个距离就能避开 ...
- Android P 适配指南
Android P 适配指南 原创: 王超 刘望舒 1周前 作者:王超1478507995000 https://juejin.im/post/5b8959f96fb9a019fe685eb3 本文由 ...
- Android P(3)---Android P版本刘海屏适配指南
Android P版本刘海屏适配指南 Android P预览版增加了很多亮点新特性,其中最接地气.最直观的改变当属适配了类似于华为P20的顶部凹槽屏幕设计这一项,俗称刘海屏. 在开发者模式中,Andr ...
- Android系统---- 全面屏(18:9屏幕)适配指南
7535 Android手机 全面屏(18:9屏幕)适配指南 从小米MIX 1发布以来,越来越多所谓"全面屏"手机发布,如三星S8,小米MIX2,VIVO X20,Goog ...
最新文章
- 蓝牙写入数据库_蓝牙 数据写入 简单易懂版(适合没写过蓝牙的看)
- ubuntu首次给root用户设置密码和root用户登录设置
- android listview左滑删除
- Linux从入门到精通系列之PPTP
- 单片机实验:交通灯控制
- 明明的随机数冒泡排序c 语言,NOIP复赛 c++-明明的随机数(算法和原码参考)
- java 表达式2004的值_javaSE习题 第三章 运算符、表达式和语句
- 设系统中有三种类型的资源(A,B,C)的五个进程(P1,P2,P3,P4,P5)。A资源的数量为17,B资源的数量为5,C资源的数量为20。在T0时刻系统状态如表所示。
- python 百度文库_百度文库文档下载地址解析python版【摸索不易,还请支持】
- Android NDK 建立cocos2dx项目
- unity3d赛车游戏视频教程
- 【IC7】FPGA最高工作频率的计算方法;FPGA最大输出频率;查看handbook的PLL最高频率;Fout_ext;Fout两个参数;FPGA输出1ns脉冲
- php对接微博热搜,基于PHP爬虫的微博热搜实时监控平台
- ODrive干货 #3 ODriveTool 指令大全
- 为什么内存为什么是以字节为单位的?
- arm+linux+usb驱动开发,Linux+ARM下的USB驱动开发
- 接口测试(一)--soapui实践
- 上海矽昌通信技术有限公司自研路由芯片
- 离散系统的稳定性分析
- python集合优化实践
热门文章
- android wear无法启用,android-wear – 无法创建Android虚拟设备,“没有为此目标安装系统映像”...
- 三同轴连接器_罗森伯格射频同轴连接器之板间连接器三
- Java项目:网上图书商城系统(java+SSM+Jsp+MySQL+Redis+JWT+Shiro+RabbitMQ+EasyUI)
- 地铁框架保护的原理_地铁屏蔽门是如何保证通讯的稳定?
- 有名内部类 匿名内部类
- asp导出word中文乱码_解决文档打开乱码问题丨小工具系列
- swift中单例的创建及销毁
- 创建操作/删除多行数据的UITableView的细节
- iOS的KVO实现剖析
- iOS - 支持水平 / 垂直显示自动滚动的跑马灯控件 --- SKAutoScrollLabel 的使用和实现