58 同城 iOS 客户端 iOS11 及 iPhone X 适配实践
一、前言
前段时间 WWDC 大会上苹果推出了 iOS11 系统 和 iPhone X 新机型,相信各个 iOS 团队的开发者都已经在计划新系统和新机型的适配工作了。不得不说,新系统和新机型的发布确实是给 iOS 开发者带来了不小的工作量,因此有必要将 58 同城 iOS 客户端适配过程中遇到的问题跟大家分享一下。
二、iOS 11 UIKit
在适配 iOS 11 之前,我们首先要弄清楚 iOS 11 做了哪些改动,哪些改动会对我们现有的应用产生影响,这样有助于我们分析现象。建议大家看下 WWDC 的官方视频,视频中提到了如何适配 iOS 11 及如何设计和适配 iPhoneX 新机型:
1. UIKit’sBars
图 1 UIKit’sBar 提供 size 的方法截图
iOS11 UIKit’sBar 改动很大,对现有的应用来说 UINavigationBar
的 titleView 适配应该是最大的问题,LargeTitle 虽然看起来似乎是很大的改动,但是实际上对现有的应用没有太多的影响。iOS11 之后 UINavigationBar 和 UIToolbar 支持 Auto Layout ,开发者必须要提供自定义视图的 size。如果自定义 titleView 中使用了 Auto Layout,那么通过设置自定义 titleView 的 frame 的方式来设定 size 在某些场景下可能就不再合适了,开发者应该着重注意下自定义 view 内部的约束(具体案例将在下文中阐述)。如图 1 所示,我们可以通过以下三种方式来提供 size:
- 约束自身宽高;
- 实现 intrinsicContentSize 方法;
- 利用子视图宽高及间距来约束 titleView;
2. UIScrollView
图 2 安全区域示意图
iOS11 引入了安全区域的概念,默认情况下安全区域是指 NavigationBar 以下 TabBar 以上的区域(iPhoneX 则不包括底部的虚拟 Home 区域),也就是说 StatusBar、NavigationBar、TabBar、虚拟 Home 都不是安全区域(可参见图 2)。为了帮助开发者判断各个 view 与安全区域的距离关系,iOS11 在 UIView 加入了一个属性:
需要注意的是这个属性是描述 view 与页面安全区域的距离关系,如果 view 的某个方向超过了安全区域则这个方向的数值为正数,如果 view 完全在安全区域内则 safeAreaInsets 的值全为 0。如在图 3 所示一个空白页面中。
图3 安全区域测试图
红、蓝 view 是父子视图的关系,且蓝色子视图完全在红色视图内,由于红、蓝两个 view 顶部超过安全区域 44 点(状态栏 44 高度),所以它们的 safeAreaInsets
都为 {44, 0, 0, 0}
,也就是说在同一个控制器中,他们的安全区域是一致的,与视图层级无关。在一个控制器中,安全区域并不是固定不变的,可以通过 ViewController 的 additionalSafeAreaInsets
方法来修改页面的安全区域,如果此时将安全区域上延 11 个点。
那么它们的 safeAreaInsets 都会变为 {33, 0, 0, 0}
。
我们再来看 UIScrollView
新增加的两个很重要的属性:
iOS 11 中 adjustedContentInset
是用来调整 scrollView 内容边距的属性,这个属性实际上是 contentInset
和 safeAreaInsets
在各个方向上的加和,即 contentInset+safeAreaInsets
。contentInsetAdjustmentBehavior
是控制采取何种策略来控制调整边距的属性,默认为 UIScrollViewContentInsetAdjustmentAutomatic
。在 iOS 11 之前,控制是否自动调整内边距的属性是 UIViewController
的 automaticallyAdjustsScrollViewInsets
,但是这个属性现在已经废弃,取而代之的是 UIScrollView
的 contentInsetAdjustmentBehavior
。contentInsetAdjustmentBehavior
共有四种设置,如图 4 所示:
图 4 contentInsetAdjustmentBehavior
如果 UIScrollView
需要调整内容边距则加上安全区域的偏移,即 adjustedContentInset = contentInset+safeAreaInsets
,如果不调整则 safeAreaInsets
不参与到计算中,即 adjustedContentInset = contentInset
。简而言之,contentInsetAdjustmentBehavior
就是告诉 UIScrollView
在计算 adjustedContentInset
时要不要加上 safeAreaInsets
。
3. UITableView
UITableView
除了继承自 UIScrollView
的特性外,还有自身 API 的变动。新系统中,UITableView
开启了估算行高,estimatedRowHeight
、 estimatedSectionHeaderHeight
、estimatedSectionFooterHeight
不再默认是 0,而是 UITableViewAutomaticDimension
(这个值打印输出是 -1),这一举措旨在帮助开发者提高性能,减少 heightForRowAtIndexPath:
方法的的调用次数,但是这样会导致 API 执行顺序发生变化。在没有开启估算行高之前 tableView
总是先执行:
再执行
API 调用顺序如图 5 所示:
图 5 关闭预估高度方法执行顺序打印
而开启估算行高之后 tableView
会先执行:
再执行:
方法调用次数减少且调用顺序变为如图 6 所示:
图 6 开启预估高度方法执行顺序打印
由此可见:
的调用次数大大减少。但是如果开发者实现:
方法,那么无论是否关闭 estimatedRowHeight
,API 的调用顺序和调用次数都是一样的,如图 7 所示。
图 7 实现预估高度方法后方法执行顺序打印
因此我们要注意新系统中 API 执行顺序和执行次数的变化。
除此之外我们还应该注意在开启预估高度tableView.estimatedSectionHeaderHeight = UITableViewAutomaticDimension;
的情况下,如果不实现
和
那么
和
是不会被执行的。
三、58 同城的实战经历
适配 iPhoneX 首先要添加一个 1125x2436 的启动图,否则会在屏幕的上方和下方留下两道黑框。在适配开始前先简单介绍下 58 同城 iOS 客户端的页面结构,因为页面结构不同可能遇到的问题会不尽相同。58 同城 iOS 客户端的 key window 的 rootViewController
是一个导航控制器,而不是 UITabBarController
。导航控制器的 rootViewController
是一个 UITabBarController
样式(非 UITabBarController
)的 ViewController
。了解了应用的结构之后,下面介绍下 58 同城 iOS 客户端适配过程中遇到的比较典型的问题:
1. 首页
先看下适配之前的首页,如图 8 所示:
图 8 适配前首页图
适配前,首页主要存在的问题有 3 个:
- 顶部的天气与状态栏有重叠;
- 首页的动画默认已经进行了一部分;
- 底部的 tabBar 在非安全区域内。
先看问题 1,产生问题 1 的原因是由于 iPhoneX 的状态栏高度产生了变化,由 20 变成了 44,天气组件的位置没有预留出新增的 24 点,导致与状态栏重叠。解决的办法是对顶部 view 进行重新布局约束,根据机型来判断是够需要增加 24 点。机型的判断由于没有真机验证,我们无法准确地获取到设备名称,因此暂时采用设备高度来判断机型。如果屏幕高度为 812,则暂时可以认为是 iPhoneX:
问题 2 的原因是 tableView
的安全区域的引起的。由于首页 tableView
的 contentInsetAdjustmentBehavior
默认为 UIScrollViewContentInsetAdjustmentAutomatic
, 在隐藏导航栏的情况下 tableView
的内容偏移为安全区域的 44 点,因此 tableView
自动偏移了 44 点,看起来像动画进行了一半。解决方法是将 contentInsetAdjustmentBehavior
设置为 never 忽略安全区域偏移。
问题 3 的原因是 iPhoneX 新增了 34 点高度的虚拟 home 区域替代了 home 实体键。虚拟 home 区域并不是安全区域,因此需要在 iPhoneX 的机型上,屏幕底部预留出 34 点高度。
适配后首页在 iPhoneX 上的效果如图 9:
图 9 适配后首页图
2. 列表
在适配过程中,我们发现在二手物品列表,上拉加载数据时底部自定义的 tabBar 会来回反复地进行显示隐藏,tabBar 为下图蓝框内区域,如图 10 所示:
图 10 带有 tab 的列表图
经过排查我们发现 tableView
的 contentOffset
在有新的内容被添加进来的时候会产生跳动,如:不断上拉的一个列表,列表的 contentOffset
的 Y 值会不断增大,如果上拉过程中触发网络请求添加了新的内容到列表中,那么列表的 contentOffset
会产生一个跳动。如图 11 所示,在 contentOffset
的 Y 值为 9000 左右的时候列表新增了一页数据,导致 contentOffset
的 Y 值变为 3000 左右。
图 11 上拉加载更多时 scrollView 的偏移量 Y 值打印图
当下拉返回顶部时,contentOffset 的 Y 值会产生一系列不连续的跳动,如当前 contentOffset 的 Y 值为 3600,下拉后变为 3500 左右然后又变为 3600 左右,如图 12 打印所示:
图 12 下拉返回时 scrollView 的偏移量 Y 值打印图
如果利用 contentOffset
的 Y 值变化判断滑动方向的话,那么现在会判断为方向时而向上时而向下,导致底部的 tabBar 反复隐藏显示。
问题根本原因是由于
方法中获取 aScrollView
的偏移量会存在跳动的现象。具体的解决办法与各自的实现逻辑有较强的关联,在此不进行详细阐述。
3. IM
在使用 iOS11 beta 版时我们发现 IM 会话页的很多消息都变成了未识别消息类型,图 13 中左侧为适配前效果,右侧为适配后效果:
图 13 IM 会话页适配前后对比图
当消息滑出屏幕再滑回来时消息才被正确解析出来。最开始以为是 Xcode9 beta 版的 bug,因为只有 Xcode 9 打包安装的应用才会出现这种问题,而通过 Xcode8 正式版打包后安装到 iOS11 的手机上并不会出现这种问题。不过随着 beta 版的不断稳定,这种现象并没有随之消失,因此我们觉得有必要查看下消息不识别的具体原因。经过排查后我们发现,异常情况出现的原 l 因是由于 tableView 没有取到可重用的 cell,代码如图 14:
图 14 tableView 获取 Cell 的方法图
会话页将未取到 cell 作为一种异常情况进行了处理,处理方式是将这种情况当做未识别消息展示,处理逻辑如图 15 所示:
图 15 IM 会话列表异常处理代码
经过分析,取不到 cell 很有可能是因为 cell 没有被注册,而之前代码中注册 cell 的逻辑在
方法中。由于 iOS11 默认开启了 estimatedRowHeight,导致
方法先执行,
后执行,因此 cell 没有被注册成功,代码走进了异常处理逻辑。为了尽量不修改之前的业务逻辑,我们采用的适配方式的将估算高度关闭。
除此之外,IM 还存在适配安全区域的问题。未适配之前,IM 会话页面的输入框在 iPhoneX 的非安全区域内,见图 16 标注区域。
图 16 IM 会话页底部输入未适配前图
因此页面的底部需要设置 34 点的边距。iPhoneX 与其他手机很明显的不同就是键盘的高度,iPhoneX 的键盘高度为 333,这个键盘是从屏幕底部抬起并跨过了底部的虚拟 home 区域,键盘在安全区域内的高度为(333-34)。因此在监听键盘的事件中要注意对抬起高度的处理,以 IM 会话页为例,在非 iPhoneX 手机上输入框会上升 keyboardHeight 的高度,但是在 iPhoneX 上则只需要上升 keyboardHeight-34 点的高度。在 iPhoneX 手机上我们要格外注意键盘高度和安全区域结合带来的潜在问题。
4. 发布
在适配发布页面的时候,我们发现小区搜索页面的 titleView 在 iOS11 手机上变得很小,没有完全展开,效果如图 17 所示:
图 17 小区选择未适配前图
图 16 中的导航栏的 titleView 是 WBPUBSearchBar
类型的。由于自定义的 WBPUBSearchBar
是原来是通过 initWithFrame:
来创建并指明宽高的,WBPUBSearchBar
内部布局是依赖 Masonry 的,但是布局并没有设置 textFiled 和 button 的宽度,仅仅指明了 textFiled 和 button 各自的左右间距。在此情况下,自定义的 titleView 无法依赖内部子视图的约束得到正确的宽度,因此 titleView 的宽度并不正确。因此需要实现方法:
向外界提供自身的 size。当然除了实现 intrinsicContentSize 方法外也可以通过添加自身约束的方式来设置自身的正确合理的 size。
5. 视频
58 同城的二手房详情页支持视频展示房源,当全屏播放视频的时候会导致虚拟 home 条遮挡住视频内容,适配前视频全屏播放效果如图 18。
图 18 房源视频全屏播放图
由于在播放视频的时候有交互较少,苹果允许开发者在当前页面隐藏虚拟 home 条。因此可以在视频播放控制器中实现下述方法来隐藏虚拟 home 条:
- 在适当时机刷新虚拟 home。
- 实现 prefersHomeIndicatorAutoHidden 方法,返回是否需要隐藏虚拟 home。
当用户触碰屏幕时虚拟 home 会再次出现。这样就可以解决横屏虚拟 home 遮挡屏幕的问题。
6. 宠物
在使用 iOS11 beta 版时我们发现导航栏的自定义返回按钮距离屏幕左侧过远。这种情况只有在 Xcode9 编译时才会出现,Xcode8 编译打包在 iOS11 beta 的手机上运行并不会出现这种问题。对比导航栏的视图层级结构我们发现导航栏的层级做了很大的变动。
图 19 iOS 11 前导航栏层级结构和实际展示图
图 20 iOS11 导航栏层级结构和实际展示图
在 iOS11 下设置 UIBarButtonItem 时,系统新增了 _UINavigationBarContentView
和 _UIButtonBarStackView
两层 View,新增的两层 frame 是有偏移的,而且布局采用的是约束方式。因此需要遍历层级找到 _UIButtonBarStackView
这层视图并修改它与 _UINavigationBarContentView
间的约束,修改约束的时机放自定义导航控制器的在 viewDidLayoutSubviews
方法中。修改约束关键代码如图 21 所示:
图 21 修改约束关键代码
四、总结
总结一下,58 同城 iOS 客户端在适配过程中遇到了以下问题:
由于安全区域的引入,引起了 UIScrollView 的偏移。安全区域引起的 UIScrollView 偏移导致的问题非常多,文中仅仅以首页为例向大家展示了 58 同城的解决方式。当然,解决方式有很多种,只要了解问题的原因,那么解决方式可以根据自己的场景自由决定。文中没有提及 RN 和 web 的适配方式,主要原因是 RN 和 web 的适配方式与 Native 的适配方式类似,最主要的工作仍然是适配安全区域。
tableView 的变化总体而言还是很大的,因此需要我们仔细观察现象排查代码。
对于导航栏的适配,我们应该注意 titleView 的内部约束,返回按钮的适配需要注意的是修改约束的时机。
键盘抬起本身并不会引起适配问题,但是涉及到安全区域的适配可能就会导致 UI 处理不正确。
开发者应该根据场景来控制虚拟 home 的显示和隐藏。
以上是 58 同城 iOS 客户端在适配过程中遇到的问题。当然,实际适配过程中遇到的问题远比文中描述的多,且情况要更为复杂。文中只是列出了一些比较典型的问题,希望对读者有所帮助。
(http://blog.csdn.net/csdnnews/article/details/78127641)
58 同城 iOS 客户端 iOS11 及 iPhone X 适配实践的更多相关文章
- 转载:MongoDB 在 58 同城百亿量级数据下的应用实践
为什么要使用 MongoDB? MongoDB 这个来源英文单词“humongous”,homongous 这个单词的意思是“巨大的”.“奇大无比的”,从 MongoDB 单词本身可以看出它的目标是提 ...
- iOS开发点滴:iPhone屏幕适配
最近开始做iOS开发,遇到一些小问题和解决方法,记录下. 今天是iPhone屏幕适配 iPhone5出来之后屏幕就有iPhone就有了2种尺寸:3.5寸和4寸,xcode 5 的IB设计器里面界面 ...
- iOS之iOS11、iPhone X、Xcode9 适配指南
更新iOS11后,发现有些地方需要做适配,整理后按照优先级分为以下三类: 1.单纯升级iOS11后造成的变化: 2.Xcode9 打包后造成的变化: 3.iPhoneX的适配 一.单纯升级iOS11后 ...
- iOS11 与 iPhone X适配的那些坑(持更中...)
目录 问题列表 1.适配iPhoneX 屏幕原则 2.适配过程一些常量的设置 3..iPhone X 上运行有黑色区域问题 4.iOS11导航栏适配 5.出现UIScrollview 漂移问题(基本都 ...
- ios loading视图动画(模仿58同城)
最近看了58同城的加载视图,感觉很不错,如下图: 所以想模仿写一个,下载58同城的app,解压,发现它用的是图片来实现的动画效果, 并不是绘制出来的,所以这就相对简单些了,其实整个动画的逻辑不复杂,无 ...
- 58同城高性能移动Push推送平台架构演进之路
本文详细讲述58同城高性能移动Push推送平台架构演进的三个阶段,并介绍了什么是移动Push推送,为什么需要,原理和方案对比:移动Push推送第一阶段(单平台)架构如何设计:移动Push推送典型性能问 ...
- 转: 58同城高性能移动Push推送平台架构演进之路
转: http://geek.csdn.net/news/detail/58738 文/孙玄 本文详细讲述58同城高性能移动Push推送平台架构演进的三个阶段,并介绍了什么是移动Push推送,为什么需 ...
- CSDN首页> 云计算 孙玄:解析58同城典型技术架构及演变
转:http://www.csdn.net/article/2015-04-09/2824437 在UPYUN主办的“UPYUN Open Talk”第三期北京站上,58同城系统架构师孙玄详细介绍了5 ...
- 改进iOS客户端的升级提醒功能
改进iOS客户端的升级提醒功能 功能设计 先申明一下,我是码农,不是一个产品经理,但我觉得现有市面上的很多 App,设计的 "升级提示功能" 都不太友好.在此分享一下我的想法,欢迎 ...
随机推荐
- HighCharts之2D面积图
HighCharts之2D面积图 1.HighCharts之2D面积图源码 <!DOCTYPE html> <html> <head> <meta char ...
- Error:Error #2174
1.错误描述 Error:Error #2174 : 对于每个FileReference,每次只能执行一个下载.上载.加载或保存操作 2.错误原因 Flex中,在做单文件上传时,多次点击"上 ...
- 如何把Excel中的E+数值批量修改为文本格式?
日常工作中,经常会出现这样的情况,当我们把一组数据导入EXCEL表中时,本想让数字在表中全部显示出来,但是表格中却以E+的方式显示,如果数据较少,我们可以用最笨的方法一个一个的点击单元格来实现目的,但 ...
- Fragment加载方式与数据通信
一.加载方式 1. 静态加载 1.1 加载步骤 (1) 创建fragment:创建自定义Fragment类继承自Fragment类,同时将自定义Fragment类与Fragment视图绑定(将layo ...
- 洛谷P4180 [Beijing2010组队]次小生成树Tree(最小生成树,LCT,主席树,倍增LCA,倍增,树链剖分)
洛谷题目传送门 %%%TPLY巨佬和ysner巨佬%%% 他们的题解 思路分析 具体思路都在各位巨佬的题解中.这题做法挺多的,我就不对每个都详细讲了,泛泛而谈吧. 大多数算法都要用kruskal把最小 ...
- Js表单验证控件(使用方便,无需编码)-01使用说明
演示地址:http://weishakeji.net/Utility/Verify/Index.htm 开源地址:https://github.com/weishakeji/Verify_Js ...
- mailgun 发邮件示例代码Python版
1 首先到mailgun官网注册账号,并激活账号 点击domains,进入默认的域名,最底下那个sandbox域名就是默认的测试域名 如果自己有域名,也可以添加自己的域名测试,具体参考:ssr pan ...
- mount挂接命令使用
挂接 操作系统 1.-t vfstype 指定文件系统的类型,通常不必指定.mount 会自动选择正确的类型.常用类型有: 光盘或光盘镜像:iso9660 DOS fat16文件系统:msdos Wi ...
- VHDL和verilog应该先学哪个?
网上有太多的VHDL和verilog比较的文章,基本上说的都是VHDL和verilog之间可以实现同一级别的描述,包括仿真级.寄存器传输级.电路级,所以可以认为两者是等同级别的语言.很多时候会了其中一 ...
- SQL Server 历史SQL执行记录
编程执行Sql语句难免忘记保存执行的文本,或是意外设备故障多种情况的发生.对于写的简单的Sql语句丢了就丢了,但对于自己写的复杂的丢失就有些慌了, 有时候很难再次写出来,这时候就需要用一些方法找回Sq ...