参考文章:
WWDC 2017 session204: Updating Your App for iOS 11
Apple 官方文档: Human Interface Guidelines
iPhone X 中文官方适配文档
你可能需要为你的 APP 适配 iOS11
iOS11 导航栏按钮位置问题的解决
iOS11 遇到的坑及解决方法
适配 iOS11&iPhoneX 的一些坑
iOS11 & iPhoneX 适配 & Xcode9 打包注意事项
App 界面适配 iOS11(包括 iPhoneX 的奇葩尺寸)
简书 App 适配 iOS 11
Xcode9 打包报错问题解决
iOS11 访问相册权限变更问题
The Ultimate Guide To iPhone Resolutions
iPhoneX状态条的隐藏与显示
首先,请注意工程中依赖的第三方代码(framework, library或是源码),需要留意其适配 iOS11、iPhoneX 的更新。
自己的代码,可以参照下面整理的适配。
一. 先来热个身,^_^
1. 每次苹果发布新的系统,我们都要注意下新系统相关宏的支持、废弃 API 的替换:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
2. 每次苹果发布新的设备,我们也要注意下新设备相关宏的支持:
更多新设备信息详见: Github-iOS-getClientInfo
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
- 7
下图是 iPhone 常见型号的屏幕相关对比:
更详细的信息可查阅:The Ultimate Guide To iPhone Resolutions
iPhone 8 与 iPhone X 的尺寸对比:
二. 状态栏
iPhone X状态条由20px变成了44px,UITabBar由49px变成了83px。设置布局时y直接写成64的就要根据机型设置。可以设置宏
- 1
然后再设置。
三. 导航栏
1. 导航栏高度的变化
iOS11之前导航栏默认高度为64pt(这里高度指statusBar + NavigationBar),iOS11之后如果设置了prefersLargeTitles = YES则为96pt,默认情况下还是64pt,但在iPhoneX上由于刘海的出现statusBar由以前的20pt变成了44pt,所以iPhoneX上高度变为88pt,如果项目里隐藏了导航栏加了自定义按钮之类的,这里需要注意适配一下。
2. 导航栏图层及对titleView布局的影响
iOS11之前导航栏的title是添加在UINavigationItemView上面,而navigationBarButton则直接添加在UINavigationBar上面,如果设置了titleView,则titleView也是直接添加在UINavigationBar上面。iOS11之后,大概因为largeTitle的原因,视图层级发生了变化,如果没有给titleView赋值,则titleView会直接添加在_UINavigationBarContentView上面,如果赋值了titleView,则会把titleView添加在_UITAMICAdaptorView上,而navigationBarButton被加在了_UIButtonBarStackView上,然后他们都被加在了_UINavigationBarContentView上,如图:
所以如果你的项目是自定义的navigationBar,那么在iOS11上运行就可能出现布局错乱的bug,解决办法是重写UINavigationBar的layoutSubviews方法,调整布局,上代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
再补充一点,看了简书App适配iOS11发现titleView支持autolayout,这要求titleView必须是能够自撑开的或实现了- intrinsicContentSize方法
- 1
- 2
- 3
3. 控制大标题的显示
在 UI navigation bar 中新增了一个 BOOL 属性prefersLargeTitles
,将该属性设置为ture,navigation bar
就会在整个APP中显示大标题,如果想要在控制不同页面大标题的显示,可以通过设置当前页面的navigationItem
的largeTitleDisplayMode
属性;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
4. Navigation 集成 UISearchController
把你的UISearchController
赋值给navigationItem
,就可以实现将UISearchController
集成到Navigation
。
- 1
- 2
使用Xcode9 编译后发现原生的搜索栏样式发生改变,如下图,右边为 iOS 11 样式,搜索区域高度变大,字体变大。
5. UINavigationController 和滚动交互
滚动的时候,以下交互操作都是由UINavigationController
负责调动的:
- 1
- 2
- 3
所以,如果你使用navigation bar
,组装一些整个push和pop体验,你不会得到searchController
的集成、大标题的控制更新和Rubber banding
效果,因为这些都是由UINavigationController
控制的。
6. UIToolbar and UINavigationBar — Layout
在 iOS 11 中,当苹果进行所有这些新特性时,也进行了其他的优化,针对 UIToolbar 和 UINavigaBar 做了新的自动布局扩展支持,自定义的 bar button items、自定义的 title 都可以通过 layout 来表示尺寸。
需要注意的是,你的constraints
需要在 view 内部设置,所以如果你有一个自定义的标题视图,你需要确保任何约束只依赖于标题视图及其任何子视图。当你使用自动布局,系统假设你知道你在做什么。
7. Avoiding Zero-Sized Custom Views
自定义视图的size为0是因为你有一些模糊的约束布局。要避免视图尺寸为 0,可以从以下方面做:
-
UINavigationBar 和 UIToolbar 提供位置
-
开发者则必须提供视图的 size,有三种方式:
-
对宽度和高度的约束;
-
实现 intrinsicContentSize;
-
通过约束关联你的子视图;
-
8. 导航栏按钮的位置问题
在 iOS7 之后,我们在设置 UINavigationItem 的 leftBarButtonItem,rightBarButtonItem 的时候都会造成位置的偏移,虽然不大,但是跟 UI 的设计或者国人的习惯有点区别,当然也有很好的解决方案,多添加一个消极的宽度为负值的 UIBarButtonItem
- 1
- 2
- 3
- 4
- 5
- 6
在我们添加导航栏按钮的时候
我们使用就可以满足将按钮位置调整的需求
- 1
但是这些在iOS 11中都无效了!!!!
但是这些在iOS 11中都无效了!!!!
但是这些在iOS 11中都无效了!!!!
重要的事情说3遍。
iOS 11改动相当大的就是导航栏的部分,在原来的已经复杂的不要的图层中又新增了新的图层!
是的你没有看做,_UINavigationBarContentView和_UIButtonBarStackView和_UITAMICAdaptorView
而我们之前的leftBarButtonItem什么的现在都在UIButtonBarStackView中了.更无语的是这些
- 1
- 2
- 3
- 4
- 5
我们可以看到一个_UIButtonBarStackView占掉了12个像素的左边约束,_UITAMICAdaptorView又占据了8个像素的左边约束,所以说我们很无语的就被占据了20px,更可气的是,都是私有对象,不容易修改!
于是还是老套路,我们设置负值来调整约束,结果却失败了,无效…
迫于无奈,我们只能想新的办法。
- 放弃UIBarButtonItem,放弃UINavigationBar,使用自定义视图代替
- 在UINavigationBar中使用添加视图的方式,固定位置固定大小添加按钮
- UIBarButtonItem.customView 设置偏移(比如按钮设置图片偏移 视图设置tranform等)
- 修改UIBarButtonItem图层结构(删除图层,或者修改约束)
当然,完全的使用自定义视图代替原生的UINavigationBar和UIBarButtonItem,这里我也不需要说明了.就是自定义视图蛮,肯定都能解决
使用addSubview:添加,之后remove什么的虽然可以,但是这个也不是我想要的
至于这是偏移,结果也嗯惨淡,无效.我尝试了设置旋转都可以,但是设置位置左移就失效了.很无语
为什么非要大动代码呢?在iOS 11之前,我们的项目绝大部分都是使用UINavigationBar和UIBarButtonItem,也就是系统的来管理,现在如果因为一个偏移问题,我们就要修改过多代码,岂不是很麻烦?
能否有较小代价实现?
答案是有的。
我们可能会做这样的一个分类
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
在我们iOS11之前,我们使用这样的一个分类来扩展,
使得我们在vc中就能这样使用
- 1
就能调整好我们的按钮位置
那么能不能不懂这些代码也满足iOS 11呢?
那么只有在加一点点东西了,在分类中
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
在什么地方写我们都能想明白,接下来是怎么写的问题了
我的思路是既然他是一个customView,那么我能否扩展这个customView呢?
我们原来将一个按钮直接用作customView,比如这样
- 1
但是现在我想的是按钮添加在一个我们定义的view中,view作为customView
这样view作为一个位置调整的视图,就可以相对自由的定义了
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
那么这个view我们也能干些事情了
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
代码其实不复杂,就是遍历view的父视图,当其实UIStackView的时候,我们修改其左右约束,但是仅仅修改的话会造成约束冲突,所以我们还需要提前移除约束冲突的左右约束(如果担心影响问题,不移除没有关系,仅仅是编译器会报约束冲突log,代码洁癖的话会感觉不舒服)
于是在原来的分类中稍作扩展,我们的新的分类就完成了
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
使用前:
使用后:
我不需要需改任何界面上的代码,在iOS 11下解决了导航栏按钮位置问题
当然你也能在做扩展,是偏移多少,修改约束的值即可
上面部分代码省略,完整demo请移步下载
使用中可能会遇到的问题及解决方法:
1. 某一个界面在push一个新界面之后再返回回来之后位置就还原了
解决方案其实很简单,只要将设置leftItem的方法写在viewWillAppear中即可,这样即可保证约束不会被系统重置
2. demo中的删除约束的判断仅仅是我个人项目中的判断,每个开发者的项目因为各种因素可能会有不同的影响,大家可以根据项目自行判断需要删除的约束条件,亦或者是不删除约束也是可以的
上面的问题另外一个解决方法:
使用layoutMargins这个属性
我们遍历图层大致可以看到这样的
- 1
这个UINavigationBarContentView平铺在导航栏中作为iOS11的各个按钮的父视图,该视图的所有的子视图都会有一个layoutMargins被占用,也就是系统调整的占位,我们只要把这个置空就行了.那样的话该视图下的所有的子视图的空间就会变成我们想要的那样,当然为了保险起见,该视图的父视图也就是bar的layoutMargins也置空,这样 整个bar就会跟一个普通视图一样了 左右的占位约束就不存在了
于是就出现了这样的代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
是的,这一次的修正方式何其的轻松,之前饶了太多的弯路….
于是在结合iOS11之前的特性,和并出新的解决导航栏按钮问题的新的解决方案,
这一次,修正的更加彻底
相较于上一次的优势,
1.可以使用itmes方式设置多个按钮
2.可以不写在viewWillAppear中也可以满足push和pop不更改约束的问题
3.不对约束进行修改,修改的是layoutMargins,使其默认的20变成0,这样不影响导航栏中其他视图的约束冲突问题
4.代码量不重,和之前不通,这次仅仅是调整layoutMargins,不需要为了修改约束等再添加图层等,具体可以看我之前的,比较写法差异
5.最后代码也更加简洁.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
效果和之前的解决方案几乎一样,只能说这次是换了思路实现的
可以很明显的看到间距不是20,至于是多少?
我用宏定义的方式设置的,你也可以自定义,或者使用其他的方式确定其大小。
9. 导航栏的边距变化
在iOS11对导航栏里面的item的边距也做了调整:
(1)如果只是设置了titleView,没有设置barbutton,把titleview的宽度设置为屏幕宽度,则titleview距离屏幕的边距,iOS11之前,在iPhone6p上是20p,在iPhone6p之前是16p;iOS11之后,在iPhone6p上是12p,在iPhone6p之前是8p。
(2)如果只是设置了barbutton,没有设置titleview,则在iOS11里,barButton距离屏幕的边距是20p和16p;在iOS11之前,barButton距离屏幕的边距也是20p和16p。
(3)如果同时设置了titleView和barButton,则在iOS11之前,titleview和barbutton之间的间距是6p,在iOS11上titleview和barbutton之间无间距,如下图:
10. 导航栏返回按钮
之前的代码通过下面的方式自定义返回按钮(可以隐藏返回按钮的标题)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
iOS 11 中setBackButtonTitlePositionAdjustment:UIOffsetMake没法把按钮移出navigation bar。
解决方法是设置navigationController的backIndicatorImage和backIndicatorTransitionMaskImage
- 1
- 2
- 3
iOS 11 想通过setBackButtonTitlePositionAdjustment:UIOffsetMake隐藏返回按钮文字,可以像下面这样做适配:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
四. 管理 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
结构体类型的属性:
- 1
- 2
- 3
layoutMargins
是UIEdgeInsets
结构体类型的属性:
- 1
- 2
- 3
从上面两种结构体的对比可以看出,NSDirectionalEdgeInsets
属性用 leading 和 trailing 取代了之前的 left 和 right。
directional layout margins属性的说明如下:
- 1
- 2
例子:当你设置了trailing = 30;当在一个right to left 语言下trailing的值会被设置在view的左边,可以通过layoutMargin的left属性读出该值。如下图所示:
还有其他一些更新。自从引入layout margins
,当将一个view添加到viewController
时,viewController
会修复view的layoutMargins
为UIKit定义的一个值,这些调整对外是封闭的。从iOS11开始,这些不再是一个固定的值,它们实际是最小值,你可以改变view的layoutMargins
为任意一个更大的值。而且,viewController
新增了一个属性:viewRespectsSystemMinimumLayoutMargins
,如果你设置该属性为”false”,你就可以改变你的layoutMargins
为任意你想设置的值,包括0,如下图所示:
五. 安全区域(Safe Area)
在 iOS 11 上运行 tableView 向下偏移 64px 或者 20px,因为 iOS 11 废弃了 automaticallyAdjustsScrollViewInsets,而是给 UIScrollView 增加了 contentInsetAdjustmentBehavior 属性。避免这个坑的方法是要判断
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
还有的发现某些界面tableView的sectionHeader、sectionFooter高度与设置不符的问题,在iOS11中如果不实现 -tableView: viewForHeaderInSection:和-tableView: viewForFooterInSection: ,则-tableView: heightForHeaderInSection:和- tableView: heightForFooterInSection:不会被调用,导致它们都变成了默认高度,这是因为tableView在iOS11默认使用Self-Sizing,tableView的estimatedRowHeight、estimatedSectionHeaderHeight、 estimatedSectionFooterHeight三个高度估算属性由默认的0变成了UITableViewAutomaticDimension,解决办法简单粗暴,就是实现对应方法或把这三个属性设为0。
如果你使用了Masonry,那么你需要适配safeArea
- 1
- 2
- 3
- 4
- 5
如下图:照片应用程序
从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
值时,有两种方式获取到回调:
- 1
- 2
六. UIScrollView
如果有一些文本位于UI滚动视图的内部,并包含在导航控制器中,现在一般navigationContollers会传入一个contentInset给其最顶层的viewController的scrollView,在iOS11中进行了一个很大的改变,不再通过scrollView的contentInset属性了,而是新增了一个属性:adjustedContentInset,通过下面两种图的对比,能够表示adjustContentInset表示的区域:
新增的contentInsetAdjustmentBehavior属性用来配置adjustedContentInset的行为,该结构体有以下几种类型:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
七. UITableView
1. 在iOS 11中默认启用Self-Sizing
。
这个应该是UITableView最大的改变。我们知道在iOS8引入Self-Sizing 之后,我们可以通过实现estimatedRowHeight相关的属性来展示动态的内容,实现了estimatedRowHeight属性后,得到的初始contenSize是个估算值,是通过estimatedRowHeight x cell的个数得到的,并不是最终的contenSize,tableView不会一次性计算所有的cell的高度了,只会计算当前屏幕能够显示的cell个数再加上几个,滑动时,tableView不停地得到新的cell,更新自己的contenSize,在滑到最后的时候,会得到正确的contenSize。创建tableView到显示出来的过程中,contentSize的计算过程如下图:
Self-Sizing在iOS11下是默认开启的,Headers, footers, and cells都默认开启Self-Sizing,所有estimated 高度默认值从iOS11之前的 0 改变为UITableViewAutomaticDimension:
- 1
如果目前项目中没有使用 estimateRowHeight 属性,在 iOS11 的环境下就要注意了,因为开启 Self-Sizing 之后,tableView 是使用 estimateRowHeight 属性的,这样就会造成 contentSize 和 contentOffset 值的变化,如果是有动画是观察这两个属性的变化进行的,就会造成动画的异常,因为在估算行高机制下,contentSize 的值是一点点地变化更新的,所有 cell 显示完后才是最终的 contentSize 值。因为不会缓存正确的行高,tableView reloadData的时候,会重新计算 contentSize,就有可能会引起 contentOffset 的变化。此外,也看到有开发者被此变化影响到 MJRefresh 上拉刷新功能。
iOS11 下不想使用 Self-Sizing 的话,可以通过以下方式关闭:
- 1
- 2
- 3
- 1
- 2
- 3
- 4
- 5
- 6
- 7
iOS11下,如果没有设置estimateRowHeight的值,也没有设置rowHeight的值,那contentSize计算初始值是 44 * cell的个数,如下图:
2. separatorInset 扩展
iOS 7 引入separatorInset属性,用以设置 cell 的分割线边距,在 iOS 11 中对其进行了扩展。可以通过新增的UITableViewSeparatorInsetReference枚举类型的separatorInsetReference属性来设置separatorInset属性的参照值。
- 1
- 2
- 3
- 4
下图清晰的展示了这两种参照值的区别:
3. 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。
4. 滑动操作(Swipe Actions)
在iOS8之后,苹果官方增加了UITableVIew的右滑操作接口,即新增了一个代理方法(tableView: editActionsForRowAtIndexPath:)和一个类(UITableViewRowAction),代理方法返回的是一个数组,我们可以在这个代理方法中定义所需要的操作按钮(删除、置顶等),这些按钮的类就是UITableViewRowAction。这个类只能定义按钮的显示文字、背景色、和按钮事件。并且返回数组的第一个元素在UITableViewCell的最右侧显示,最后一个元素在最左侧显示。从iOS 11开始有了一些改变,首先是可以给这些按钮添加图片了,然后是如果实现了以下两个iOS 11新增的代理方法,将会取代(tableView: editActionsForRowAtIndexPath:)代理方法:
- 1
- 2
- 3
- 4
这两个代理方法返回的是UISwipeActionsConfiguration类型的对象,创建该对象及赋值可看下面的代码片段:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
创建UIContextualAction对象时,UIContextualActionStyle有两种类型,如果是置顶、已读等按钮就使用UIContextualActionStyleNormal类型,delete操作按钮可使用UIContextualActionStyleDestructive类型,当使用该类型时,如果是右滑操作,一直向右滑动某个cell,会直接执行删除操作,不用再点击删除按钮,这也是一个好玩的更新。
- 1
- 2
- 3
- 4
滑动操作这里还有一个需要注意的是,当cell高度较小时,会只显示image,不显示title,当cell高度够大时,会同时显示image和title。我写demo测试的时候,因为每个cell的高度都较小,所以只显示image,然后我增加cell的高度后,就可以同时显示image和title了。见下图对比:
八. UIBarItem
WWDC通过iOS新增的文件管理App:Files开始介绍,在Files这个APP中能够看到iOS11中UIKit’s Bars的一些新特性:在浏览功能上的大标题视图(向上滑动后标题会回到原来的UI效果)、横屏状态下tab上的文字和icon会变为左右排列。我用iOS11的模拟器体验了一下Files这个APP,如下图所示:
在iPhone上,tab上的图标较小,tab bar较小,这样垂直空间可多放置内容。如果有人看不清楚tab bar上的图标或文字,可以通过长按tab bar上的任意item,会将该item显示在HUD上,这样可以清楚的看清icon和text。对tool bar 和 navigation bar同理,长按item也会放大显示。如下图显示:
UIBarItem是UI tab bar item和UI bar button item的父类,要想实现上面介绍的效果,只需要为UIBarItem 设置landscapeImagePhone属性,在storyboard中也支持这个设置,对于HUD的image需要设置另一个iOS11新增的属性:largeContentSizeImage,关于这部分更详细的讨论,可以参考 WWDC2017 Session 215:What’s New in Accessibility
九. iOS11访问相册权限变更问题
在更新 iOS11 之后,保存到相册出现 crash 现象,大家都知道访问相册需要申请用户权限。
相册权限需要在 info.plist—Property List 文件中添加 NSPhotoLibraryUsageDescription 键值对,描述文字不能为空。
iOS11 之前:访问相册和存储照片到相册(读写权限),需要用户授权,需要添加NSPhotoLibraryUsageDescription(info.plist 显示为 Privacy - Photo Library Usage Description)。
iOS11 之后:默认开启访问相册权限(读权限),无需用户授权,无需添加NSPhotoLibraryUsageDescription,适配 iOS11 之前的还是需要加的。 添加图片到相册(写权限),需要用户授权,需要添加 NSPhotoLibraryAddUsageDescription(info.plist 显示为 Privacy - Photo Library Additions Usage Description),否则可能会崩,可能会崩,可能会崩。
十. 全新的 HEIC 格式原图
对于IM的发送原图功能,iOS11 启动全新的 HEIC 格式的图片,iPhone7 以上设备 + iOS11 排出的 live 照片是”.heic”格式图片,同一张 live 格式的图片,iOS10 发送就没问题(转成了jpg),iOS11就不行
微信的处理方式是一比一转化成 jpg 格式
QQ和钉钉的处理方式是直接压缩,即使是原图也压缩为非原图
也可采取微信的方案,使用以下代码转成 jpg 格式
- 1
- 2
- 3
十一. iPhoneX
1. TouchID -> FaceID
iPhone X 只有 faceID,没有touchID,如果你的应用有使用到 touchID 解锁的地方,这里要根据设备机型进行相应的适配。
2. LaunchImage
关于iPhoneX(我就不吐槽刘海了…),如果你的APP在iPhoneX上运行发现没有充满屏幕,上下有黑色区域,那么你应该也像我一样LaunchImage没有用storyboard而是用的Assets,解决办法添加1125x2436尺寸的启动图。
3. 状态栏 和 导航栏
关于状态栏另外两个需要注意的地方:
- 不要在 iPhone X 下隐藏状态栏,一个原因是显示内容足够高了,另一个是这样内容会被刘海切割。
- 现在通话或者其它状态下,状态栏高度不会变化了,程序不需要去做兼容。
4. UITabBar
iPhoneX不止多了刘海,底部还有一个半角的矩形,使得tabbar多出来了34p的高度,不过不管导航栏和tabbar一般系统都会自动适配safeArea。
注意横屏下的 iPhoneX 的底部危险区域高度为21,UITabBar高度为32,整个底部占掉了屏幕的53高度。
5. 一些宏和常量
- 1
- 1
- 2
- 3
- 4
6. 设计原则
在设计方面,苹果官方文档 Human Interface Guidelines 有明确要求,下面结合图例进行说明:
1. 展示出来的设计布局要求填满整个屏幕
2. 填满的同时要注意控件不要被大圆角和传感器部分所遮挡
3. 安全区域以外的部分不允许有任何与用户交互的控件
上面这张图内含信息略多
- 安全区域以外的部分不允许进行用户交互的,意味着下面这些情况 Apple 官方是不允许的
- 状态栏在非安全区域,文档中也提到,除非可以通过隐藏状态栏给用户带来额外的价值,否则最好把状态栏还给用户
- 底部虚拟区是替代了传统home键,高度为34pt,通过上滑可呼起多任务管理,考虑到手势冲突,这部分也是不允许有任何可交互的控件,但是设计的背景图要覆盖到非安全区域
- 不要让 界面中的元素 干扰底部的主屏幕指示器
4. 安全区域以外的部分不允许有任何与用户交互的控件
在横屏状态下,不能因为刘海的原因将内容向左或者向右便宜,要保证内容的中心对称
5. 重复使用现有图片时,注意长宽比差异。iPhone X 与常规 iPhone 的屏幕长宽比不同,因此,全屏的 4.7 寸屏图像在 iPhone X 上会出现裁切或适配宽度显示。所以,这部分的视图需要根据设备做出适配。
7. 横屏适配
关于 safe area,使用 safeAreaLayoutGuide 和 safeAreaInset 就能解决大部分问题,但是横屏下还可能会产生一些问题,需要额外适配
问题一. 横屏模式下状态栏问题
看了下 iPhoneX 模拟器中,桌面没有横屏模式,但是所有预装 App 横屏都没有状态栏。自己新建了工程,发现代码中重写 - (BOOL)prefersStatusBarHidden 也无法让横屏下出现状态栏。但是看到网上 这篇文章(戳我可看) ,用比较 hacker 的方法实现的,重写 setNeedsStatusBarAppearanceUpdate 不做任何事情,导致竖屏切横屏没有把状态条去掉,高度应该还是横屏下的44,但是这个没有隐藏的状态条并没有影响横屏模式下从最顶部开始的 SafeArea,所以会导致适配变得有点麻烦,不能完全按照 SafeArea 那一套做相对布局了(需要考虑这种特殊 case)。
问题二. TableViewCell 的 contentView 的 frame 问题
产生这个原因代码是:[headerView.contentView setBackgroundColor:[UIColor headerFooterColor]]
这个写法看起来没错,但是只有在 iPhone X 上有问题,之前所有版本的 iPhone 上 tableView 的 cell 和它的 contentView 的大小是相同的,开发者相对 cell 布局和相对 contentView 布局效果上不会有太大区别,但是在 iPhone X 下,由于刘海和圆角的存在,tableView 的 contentView 会被裁切,所以所有的布局都应该被调整为相对 contentView 布局,否则会越界。
解决方法:设置backgroundView颜色 [headerView.backgroundView setBackgroundColor:[UIColor headerFooterColor]]
8. 滑动手势
iPhone X 最大的改变就是底部那个无时无刻不存在的 homeBar了,代替了原来home按键的功能,系统级的任务切换和回到桌面 、、,都是上滑这个细细的长条。
所以苹果爸爸的意思是:
赶紧把你自己写的上滑手势乖乖删掉~
当然如果app确实需要这个手势,可以打开程序开关覆盖系统的手势,但是这样用户就需要滑动两次来回到桌面了,这会让他们非常怀念home键。
9. 键盘区别
首先是 iPhone X 下的键盘和其他系统有区别,会多出来那个很有趣的animateEmoji工具栏,所以在做键盘相关处理的时候要关注兼容性问题,至少:高度不要写死了……
十二. Xcode 手动编译失败
1. 编译出现一堆奇葩的问题
尝试将 Applications 文件夹下的 Xcode.app 重命名为 Xcode9.app 解决了我遇到的问题。
2. Failed to read file attributes for Images.xcassets in Xcode 9
升级到 Xcode9 以后总是遇到这个奇怪的问题,上网查了下,在 stack overflow 这个帖子里面找到了答案。
Removing the reference of Images.xcassets and adding it again in Project resolved the error.
3. 第三方依赖库问题
ReactiveCocoa Unknown warning group ‘-Wreceiver-is-weak’,ignored警告
简书项目开启Treat warning as error,所有警告都会被当成错误,因此必须解决掉。
RACObserve宏定义如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
在之前的Xcode中如果消息接受者是一个weak对象,clang编译器会报receiver-is-weak警告,所以加了这段push&pop,最新的clang已经把这个警告给移除,所以没必要加push&pop了。
ReactiveCocoa已经不再维护OC版本,大多数OC开发者用的都是2.5这个版本,只能自己fork一份了,谁知github上的v2.5代码不包含对应的.podspec文件,只好到CocoaPods/Specs上将对应的json文件翻译成.podspec文件,如果你也有这个需要,可以修改Podfile如下
- 1
4. 注意事项
- Xcode9 打包版本只能是 8.2 及以下版本, 或者 9.0 及更高版本
- Xcode9 不支持 8.3 和 8.4 版本
- Xcode9 新打包要在构建版本的时候加入 1024*1024 AppStore Icon
- 拖动文件或文件夹到工程中,可能会出现代码文件或图片没有加入到 target 中,出现编译不过或者运行时图片总是没显示,需要特别注意
- Command 键复原。可在 Preferences –> Navigation –> Command-click 中选择 Jumps to Defintion 即可。
5. 一些好玩的新功能
- 鸡肋的无线调试功能(iPhone的电池…)可在 Window –> Devices and Simulators中勾选那两个选项。前提是此设备已 run 过并处于同一局域网下。
- 在 Asset 中,可以创建颜色了。右键选择 New image set,填充RGBA值或十六进制值即可。使用中直接使用新的colorwithname,参数填入创建时的名字即可。不过记得区分系统版本。
6. 模拟器新功能
-
第一时间很多公司都买不到原价的 iPhoneX 的测试机,会给测试带来不方便,可以借助模拟器安装 app 去做测试工作
启动运行模拟器:
xcrun instruments -w ‘iPhone 6 Plus’在已经启动好的模拟器中安装应用:
xcrun simctl install booted Calculator.app (这里要特别注意,是app,不是ipa 安装时需要提供的是APP的文件路径)
-
在全屏模式下使用 Xcode 模拟器
- 一次打开多个模拟器
- 缩放模拟器就像调整视窗大小一样简单
-
记录模拟器的视频
在Xcode 9官方的”What’s new”文档中,苹果声称现在可以录制模拟器屏幕视频,即使在旧版本中,只要使用simctl也可以做到,在界面上找不到地方可以启用视频录制(除了iOS 11中的内置屏幕录制)。
要获取视频档案,请执行以下代码:xcrun simctl io booted recordVideo –type=mp4
booted– 表示simctl选择当前启动的模拟器,如果你有多个已启动的模拟器,simctl将选择当前正在操作的那一个模拟器。
-
使用 Finder 共享文件到模拟器
现在,模拟器有了 Finder 扩展功能,你可以直接从 Finder 窗口共享文件。
你也可以执行以下simctl命令,使用图像/视频文件进行类似操作:
xcrun simctl addmedia booted
很高兴有这样的操作方法,但是对我而言,将文件拖放至模拟器窗口似乎快很多。
-
模拟器上打开 URL
这个也能使用simctl,所以你也可以在旧版本的模拟器上打开自定义的URL schemes。
拖拽
以你指定的任何URL执行以下命令:xcrun simctl openurl booted
关于Apple所有URL schemes的列表,请查看文档.
-
快速找到应用程序的文件夹
再来介绍一个simctl的命令,你可以使用单个命令在文件系统上获取应用程序的资料夹,只需要知道应用程序的bundle identifier并执行以下命令:
xcrun simctl get_app_container booted
或者你可以使用open命令在 Finder 中更快打开目标文件夹:
open
xcrun simctl get_app_container booted
-a Finder -
使用命令行参数(Command Line Args)在模拟器中启动应用程序
使用simctl,你也可以从终端机上启动应用程序,并在其中传递一些命令列参数(甚至可以设置一些环境变量)。如果你想在应用程序中插入一些除错行为,这将非常有用。
执行下列命令可以让你完成这项任务:xcrun simctl launch –console booted
你可以从CommandLine.arguments获取这些命令行参数(这里是文件的链接)。
- 透过Bundle ID获取完整的应用程序消息
有时找出应用程序的档案或暂存数据位于文件系统上的位置很有用,如果你需要比simctl get_app_container更全面的资讯,simctl还有一个很好用的小工具,名为appinfo,它会以下列格式显示相关资讯:
执行下面的命令并观察输出结果:
xcrun simctl appinfo booted十三. xcodebuild 打包命令修改
升级到最新的 Xcode9 以后,发现 jenkins 自动化打包失败了,后来看了下来,发现是 xcodebuild 命令签名失败,没有生成 ipa 包。在 这个帖子 中找到了解决方法。
打包脚本错误提示如下:
Error Domain=IDEDistributionSigningAssetStepErrorDomain Code=0 “Locating signing assets failed.” UserInfo={NSLocalizedDescription=Locating signing assets failed., IDEDistributionSigningAssetStepUnderlyingErrors=(
“Error Domain=IDEProvisioningErrorDomain Code=9 \”\”HLCG.app\” requires a provisioning profile with the Associated Domains and Push Notifications features.\” UserInfo={NSLocalizedDescription=\”HLCG.app\” requires a provisioning profile with the Associated Domains and Push Notifications features., NSLocalizedRecoverySuggestion=Add a profile to the \”provisioningProfiles\” dictionary in your Export Options property list.}”
)}
error: exportArchive: “HLCG.app” requires a provisioning profile with the Associated Domains and Push Notifications features.解决办法:
编辑 exportOptionsPlist 文件, 在其中添加<key>provisioningProfiles</key>
<dict> <key>com.hula.xxxxxx</key>
<string>HulaVenueDev</string> (此处名字获得见下文)
</dict>如:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>provisioningProfiles</key>
<dict>
<key>com.hula.xxxxxx</key>
<string>HulaVenueDev</string>
</dict>
<key>compileBitcode</key>
<false/>
<key>teamID</key>
<string>teamIDteamIDteamID</string>
<key>method</key>
<string>development</string>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
provisioningProfile 名可以在 apple deveploer 后台获得。也可以在 mobileprovision 文件中获得。
如图:
或 less dev.mobileprovision 找到Name