作者:sonia,腾讯移动客户端开发 工程师

商业转载请联系腾讯 WeTest 获得授权,非商业转载请注明出处。
原文链接:http://wetest.qq.com/lab/view/326.html


WeTest 导读

iOS 11 为整个生态系统的 UI 元素带来了一种更加大胆、动态的新风格。 本文介绍了 iOS11 在 UI 方面做了哪些更新,有些更新可以为用户提供更加完美的体验,但也有的可能会给目前的 APP 带来异常 bug。


前言

前几天发现在做的 APP 在 iOS11 系统上动画有异常,在其他系统的设备上都是正常的,动画的操作是观察 tableView 的 contentOffset 变化后执行的,异常动画发生在 tableView reloadData 之后,也就是说 tableView reloadData 之后,tableView 的 contentOffset 发生了几次变化。查了下资料发现原因是 iOS11 中默认开启了 Self-Sizing,在 WWDC 2017 session204 Updating Your App for iOS 11 中有介绍,因此研究了下这个 session,本文作为一个总结,下文的第三部分会有对上述的动画异常的原因分析及解决方式。

本文内容包括:集成了搜索的大标题栏、横向选项卡栏、Margins 和 Insets 以及 UIScrollView 和 UITableView 的更新和功能更强大的滑动操作。

一、在 UIKit’s Bars 中加入的新功能

WWDC 通过 iOS 新增的文件管理 App:Files 开始介绍,在 Files 这个 APP 中能够看到 iOS11 中 UIKit’s Bars 的一些新特性:在浏览功能上的大标题视图(向上滑动后标题会回到原来的 UI 效果)、横屏状态下 tab 上的文字和 icon 会变为左右排列。我用 iOS11 的模拟器体验了一下 Files 这个 APP 的竖屏和横屏,如下图所示:

(command+ 向左的箭头让模拟器横屏)

横屏时,在 iPhone 上,tab 上的图标较小,tab bar 较小,这样垂直空间可多放置内容。如果有人看不清楚 tab bar 上的图标或文字,可以通过长按 tab bar 上的任意 item,会将该 item 显示在 HUD 上,这样可以清楚的看清 icon 和 text。对 tool bar 和 navigation bar 同理,长按 item 也会放大显示。如下图显示:

1、UIBarItem

UIBarItem 是 UI tab bar item 和 UI bar button item 的父类,要想实现上面介绍的效果,只需要为 UIBarItem 设置 landscapeImagePhone 属性,在 storyboard 中也支持这个设置,对于 HUD 的 image 需要设置另一个 iOS11 新增的属性:largeContentSizeImage,关于这部分更详细的讨论,可以参考 WWDC2017 Session 215:What's New in Accessibility

2、控制大标题的显示

在 UI navigation bar 中新增了一个 BOOL 属性 prefersLargeTitles,将该属性设置为 ture,navigation bar 就会在整个 APP 中显示大标题,如果想要在控制不同页面大标题的显示,可以通过设置当前页面的 navigationItem 的 largeTitleDisplayMode 属性;

3、Navigation 集成 UISearchController

把你的 UISearchController 赋值给 navigationItem,就可以实现将 UISearchController 集成到 Navigation。

4、UINavigationController 和滚动交互

滚动的时候,以下交互操作都是由 UINavigationController 负责调动的:

所以,如果你使用 navigation bar,组装 push 和 pop 体验,你不会得到 searchController 的集成、大标题的控制更新和 Rubber banding 效果,因为这些都是由 UINavigationController 控制的。

5、UIToolbar and UINavigationBar— Layout

在 iOS 11 中,当苹果进行所有这些新特性时,也进行了其他的优化,针对 UIToolbar 和 UINavigaBar 做了新的自动布局扩展支持,自定义的 bar button items、自定义的 title 都可以通过 layout 来表示尺寸。 需要注意的是,你的 constraints 需要在 view 内部设置,所以如果你有一个自定义的标题视图,你需要确保任何约束只依赖于标题视图及其任何子视图。当你使用自动布局,系统假设你知道你在做什么。

6、Avoiding Zero-Sized Custom Views

自定义视图的 size 为 0 是因为你有一些模糊的约束布局。要避免视图尺寸为 0,可以从以下方面做:

● UINavigationBar 和 UIToolbar 提供位置

● 开发者则必须提供视图的 size,有三种方式:

① 对宽度和高度的约束;

② 实现 intrinsicContentSize;

③ 通过约束关联你的子视图;

二、管理 margins 和 insets

1、layout margins

基于约束的 Auto Layout,使我们搭建能够动态响应内部和外部变化的用户界面。Auto Layout 为每一个 view 都定义了 margin。margin 指的是控件显示内容部分的边缘和控件边缘的距离。 可以用 layoutMargins 或者 layoutMarginsGuide 属性获得 view 的 margin,margin 是视图内部的一部分。layoutMargins 允许获取或者设置 UIEdgeInsets 结构的 margin。layoutMarginsGuide 则获取到只读的 UILayoutGuide 对象。

在 iOS11 新增了一个属性:directional layout margins,该属性是 NSDirectionalEdgeInsets 结构体类型的属性:

layoutMargins 是 UIEdgeInsets 结构体类型的属性:

从上面两种结构体的对比可以看出,NSDirectionalEdgeInsets 属性用 leading 和 trailing 取代了之前的 left 和 right。

directional layout margins 属性的说明如下:

例子:当你设置了 trailing = 30;当在一个 right to left 语言下 trailing 的值会被设置在 view 的左边,可以通过 layout margins 的 left 属性读出该值。如下图所示:

还有其他一些更新。自从引入 layout margins,当将一个 view 添加到 viewController 时,viewController 会修复 view 的的 layoutMargins 为 UIKit 定义的一个值,这些调整对外是封闭的。从 iOS11 开始,这些不再是一个固定的值,它们实际是最小值,你可以改变你的 view 的 layoutMargins 为任意一个更大的值。而且,viewController 新增了一个属性:viewRespectsSystemMinimumLayoutMargins,如果你设置该属性为"false",你就可以改变你的 layout margins 为任意你想设置的值,包括 0,如下图所示:

2、安全区域(Safe Area)

如下图:照片应用程序

从 iOS 7 以来,我们在整个操作系统中都有这些半透明的 bars,苹果鼓励我们通过这些 bars 绘制内容,我们是通过 viewController 的 edgesForExtendedLayout 属性来做这些的。 iOS 7 开始,在 UIViewController 中引入的 topLayoutGuide 和 bottomLayoutGuide 在 iOS 11 中被废弃了,取而代之的就是 safeArea 的概念,safeArea 是描述你的视图部分不被任何内容遮挡的方法。 它提供两种方式:safeAreaInsets 或 safeAreaLayoutGuide 来提供给你 safeArea 的参照值,即 insets 或者 layout guide。 safeArea 区域如下图所示:

如果有一个自定义的 viewController,你可能要添加你自己的 bars,增加 safeAreaInsets 的值,可以通过一个新的属性:addtionalSafeAreaInsets 来改变 safeAreaInsets 的值,当你的 viewController 改变了它的 safeAreaInsets 值时,有两种方式获取到回调:

三、UIScrollView and UITableView 的新特性

1、 Scroll Views

如果有一些文本位于 UI 滚动视图的内部,并包含在导航控制器中,现在一般 navigationContollers 会传入一个 contentInset 给其最顶层的 viewController 的 scrollView,在 iOS11 中进行了一个很大的改变,不再通过 scrollView 的 contentInset 属性了,而是新增了一个属性:adjustedContentInset,下面的两张图的对比能够表示 adjustContentInset 表示的区域:

新增的 contentInsetAdjustmentBehavior 属性用来配置 adjustedContentInset 的行为,该结构体有以下几种类型:

2、Table Views :在 iOS 11 中默认启用 Self-Sizing

这个应该是 UITableView 最大的改变。我们知道在 iOS8 引入 Self-Sizing 之后,我们可以通过实现 estimatedRowHeight 相关的属性来展示动态的内容,实现了 estimatedRowHeight 属性后,得到的初始 contenSize 是个估算值,是通过 estimatedRowHeight x cell 的个数得到的,并不是最终的 contenSize,tableView 就不会一次性计算所有的 cell 的高度了,只会计算当前屏幕能够显示的 cell 个数再加上几个,滑动时,tableView 不停地得到新的 cell,更新自己的 contenSize,在滑到最后的时候,会得到正确的 contenSize。在测试 Demo 中,创建 tableView 到显示出来的过程中,contentSize 的计算过程如下图:

Self-Sizing 在 iOS11 下是默认开启的,Headers, footers, and cells 都默认开启 Self-Sizing,所有 estimated 高度默认值从 iOS11 之前的 0 改变为 UITableViewAutomaticDimension:

如果目前项目中没有使用 estimateRowHeight 属性,在 iOS11 的环境下就要注意了,因为开启 Self-Sizing 之后,tableView 是使用 estimateRowHeight 属性的,这样就会造成 contentSize 和 contentOffset 值的变化,如果是有动画是观察这两个属性的变化进行的,就会造成动画的异常,因为在估算行高机制下,contentSize 的值是一点点地变化更新的,所有 cell 显示完后才是最终的 contentSize 值。因为不会缓存正确的行高,tableView reloadData 的时候,会重新计算 contentSize,就有可能会引起 contentOffset 的变化。iOS11 下不想使用 Self-Sizing 的话,可以通过以下方式关闭:(前言中提到的问题也是通过这种方式解决的)

iOS11 下,如果没有设置 estimateRowHeight 的值,也没有设置 rowHeight 的值,那 contentSize 计算初始值是 44 * cell 的个数,如下图:rowHeight 和 estimateRowHeight 都是默认值 UITableViewAutomaticDimension 而 rowNum = 15;则初始 contentSize = 44 * 15 = 660;

3、Table Views:separatorInset 扩展

OS 7 引入 separatorInset 属性,用以设置 cell 的分割线边距,在 iOS 11 中对其进行了扩展。可以通过新增的 UITableViewSeparatorInsetReference 枚举类型的 separatorInsetReference 属性来设置 separatorInset 属性的参照值。

下图清晰的展示了这两种参照值的区别:

4、Table Views 和 Safe Area

有以下几点需要注意:

● separatorInset 被自动地关联到 safe area insets,因此,默认情况下,表视图的整个内容避免了其根视图控制器的安全区域的插入。

● UITableviewCell 和 UITableViewHeaderFooterView 的 content view 在安全区域内;因此你应该始终在 content view 中使用 add-subviews 操作。

● 所有的 headers 和 footers 都应该使用 UITableViewHeaderFooterView,包括 table headers 和 footers、section headers 和 footers。

5、滑动操作(Swipe Actions)

在 iOS8 之后,苹果官方增加了 UITableVIew 的右滑操作接口,即新增了一个代理方法 (tableView: editActionsForRowAtIndexPath:) 和一个类 (UITableViewRowAction),代理方法返回的是一个数组,我们可以在这个代理方法中定义所需要的操作按钮 (删除、置顶等),这些按钮的类就是 UITableViewRowAction。这个类只能定义按钮的显示文字、背景色、和按钮事件。并且返回数组的第一个元素在 UITableViewCell 的最右侧显示,最后一个元素在最左侧显示。从 iOS 11 开始有了一些改变,首先是可以给这些按钮添加图片了,然后是如果实现了以下两个 iOS 11 新增的代理方法,将会取代 (tableView: editActionsForRowAtIndexPath:) 代理方法:

这两个代理方法返回的是 UISwipeActionsConfiguration 类型的对象,创建该对象及赋值可看下面的代码片段:

创建 UIContextualAction 对象时,UIContextualActionStyle 有两种类型,如果是置顶、已读等按钮就使用 UIContextualActionStyleNormal 类型,delete 操作按钮可使用 UIContextualActionStyleDestructive 类型,当使用该类型时,如果是右滑操作,一直向右滑动某个 cell,会直接执行删除操作,不用再点击删除按钮,这也是一个好玩的更新。

滑动操作这里还有一个需要注意的是,当 cell 高度较小时,会只显示 image,不显示 title,当 cell 高度够大时,会同时显示 image 和 title。我写 demo 测试的时候,因为每个 cell 的高度都较小,所以只显示 image,然后我增加 cell 的高度后,就可以同时显示 image 和 title 了。见下图对比:

总结

大概介绍了 iOS 11 的 UI 方面的一些更新,大部分内容都用代码测试过了,有些更新确实是很实用,可以适配下 iOS 11,有的更新可能会给现有 APP 造成 bug,所以学习下这些内容还是很有必要的。

参考:

1、Updating Your App for iOS 11 - WWDC 2017 - Session 204 - iOS

2、iOS 8自动调整UITableView和UICollectionView布局

3、Mysteries of Auto Layout, Part 1 - WWDC2015

4、Mysteries of Auto Layout, Part 2 - WWDC2015

【腾讯 WeTest iOS 预审工具】

为了提高 IEG 苹果审核通过率,腾讯专门成立了苹果审核测试团队,打造出 iOS 预审工具这款产品。经过两年的内部运营,腾讯内部应用的 iOS 审核通过率从平均 35% 提升到 90%+。

现将腾讯内部产品的过审经验共享给各位。在 WeTest 腾讯质量开放平台上可使用 iOS 预审工具,点击链接:http://wetest.qq.com/product/ios 咨询体验!

如果使用当中有任何疑问,欢迎联系腾讯 WeTest 企业 QQ:800024531

iOS 预审服务

【扫描工具】上传 IPA 包、图片、视频、应用描述即可进行测试; 多维度自动扫描提审材料的被拒风险;1 小时内反馈全面的扫描报告。

【专家预审】腾讯专家为您遍历 App 所有功能模块;全面暴露 App 内容被拒风险;跟进问题直至上线(需提供官方拒绝邮件)。

【专家咨询】资深预审专家一对一服务; 咨询时间灵活可选,按需购买;有的放矢解 决审核问题。

【ASO 优化】专业团队多维度深度剖析 App 的 ASO 现状;围绕 App 目标用户群筛选高 度关联的关键词;帮助提升 App 在苹果应用商店中的曝光率。


↙↙↙阅读原文可查看相关链接,并与作者交流