iOS11 最新技能更新

来源:互联网 发布:hive sql与sql对比 编辑:程序博客网 时间:2024/05/09 02:15

一.导航栏

导航栏高度的变化

iOS11之前导航栏默认高度为64pt(这里高度指statusBar + NavigationBar),iOS11之后如果设置了prefersLargeTitles = YES则为96pt,默认情况下还是64pt,但在iPhoneX上由于刘海的出现statusBar由以前的20pt变成了44pt,所以iPhoneX上高度变为88pt,如果项目里隐藏了导航栏加了自定义按钮之类的,这里需要注意适配一下。

导航栏图层及对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
- (void)layoutSubviews {
    [super layoutSubviews];
     
    //注意导航栏及状态栏高度适配
    self.frame = CGRectMake(00, CGRectGetWidth(self.frame), naviBarHeight);
    for (UIView *view in self.subviews) {
        if([NSStringFromClass([view class]) containsString:@"Background"]) {
            view.frame = self.bounds;
        }
        else if ([NSStringFromClass([view class]) containsString:@"ContentView"]) {
            CGRect frame = view.frame;
            frame.origin.y = statusBarHeight;
            frame.size.height = self.bounds.size.height - frame.origin.y;
            view.frame = frame;
        }
    }
}

再补充一点 发现titleView支持autolayout,这要求titleView必须是能够自撑开的或实现了- intrinsicContentSize方法

1
2
3
- (CGSize)intrinsicContentSize {
    return UILayoutFittingExpandedSize;
}

二.UIScrollView、UITableView、UICollectionView

大家在iOS11设备上运行出现最多问题应该就是tableview莫名奇妙的偏移20pt或者64pt了。。原因是iOS11弃用了automaticallyAdjustsScrollViewInsets属性,取而代之的是UIScrollView新增了contentInsetAdjustmentBehavior属性,这一切的罪魁祸首都是新引入的safeArea,因为低版本直接用contentInsetAdjustmentBehavior会报警告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define  adjustsScrollViewInsets(scrollView)\
do {\
_Pragma("clang diagnostic push")\
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")\
if ([scrollView respondsToSelector:NSSelectorFromString(@"setContentInsetAdjustmentBehavior:")]) {\
    NSMethodSignature *signature = [UIScrollView instanceMethodSignatureForSelector:@selector(setContentInsetAdjustmentBehavior:)];\
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];\
    NSInteger argument = 2;\
    invocation.target = scrollView;\
    invocation.selector = @selector(setContentInsetAdjustmentBehavior:);\
    [invocation setArgument:&argument atIndex:2];\
    [invocation retainArguments];\
    [invocation invoke];\
}\
_Pragma("clang diagnostic pop")\
while (0)

还有的发现某些界面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
if (@available(iOS 11.0, *)) {
    make.edges.equalTo()(self.view.safeAreaInsets)
else {
    make.edges.equalTo()(self.view)
}


iOS 11 安全区域适配总结

一、iOS 11下APP中tableView内容下移20pt或下移64pt的原因分析

问题如下图所示:

1. 原因分析

原因是iOS 11中Controller的automaticallyAdjustsScrollViewInsets属性被废弃了,所以当tableView超出安全区域时系统自动调整了SafeAreaInsets值,进而影响adjustedContentInset值,在iOS 11中决定tableView的内容与边缘距离的是adjustedContentInset属性,而不是contentInset。adjustedContentInset的计算方式见本文第二部分内容。因为系统对adjustedContentInset值进行了调整,所以导致tableView的内容到边缘的距离发生了变化,导致tableView下移了20pt(statusbar高度)或64pt(navigationbar高度)。

如果你的APP中使用的是自定义的navigationbar,隐藏掉系统的navigationbar,并且tableView的frame为(0,0,SCREENWIDTH, SCREENHEIGHT)开始,那么系统会自动调整SafeAreaInsets值为(20,0,0,0),如果使用了系统的navigationbar,那么SafeAreaInsets值为(64,0,0,0),如果也使用了系统的tabbar,那么SafeAreaInsets值为(64,0,49,0)。关于什么情况下会发生内容下移的问题,本文第三部分有介绍。

2. 安全区域的概念

系统自动调整tableView内容偏移量,是根据安全区域来调整的。安全区域是iOS 11新提出的,如下图所示:

安全区域帮助我们将view放置在整个屏幕的可视的部分。即使把navigationbar设置为透明的,系统也认为安全区域是从navigationbar的bottom开始,保证不被系统的状态栏、或导航栏覆盖。可以使用additionalSafeAreaInsets去扩展安全区域使它包括自定义的content在界面上。每个view都可以改变安全区域嵌入的大小,Controller也可以。

safeAreaInsets属性反映了一个view距离该view的安全区域的边距。对于一个Controller的根视图而言,SafeAreaInsets值包括了被statusbar和其他可视的bars覆盖的区域和其他通过additionalSafeAreaInsets自定义的insets值。view层次中的其它view,SafeAreaInsets值反映了该view被覆盖的部分。如果一个view全部在它父视图的安全区域内,则SafeAreaInsets值为(0,0,0,0)。

二、 adjustContentInset属性的计算方式

首先看scrollView在iOS11新增的两个属性:adjustContentInset 和 contentInsetAdjustmentBehavior。

    /* Configure the behavior of adjustedContentInset.Default is UIScrollViewContentInsetAdjustmentAutomatic.*/@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior

adjustContentInset表示contentView.frame.origin偏移了scrollview.frame.origin多少;是系统计算得来的,计算方式由contentInsetAdjustmentBehavior决定。有以下几种计算方式:

1.UIScrollViewContentInsetAdjustmentAutomatic:如果scrollview在一个automaticallyAdjustsScrollViewContentInset = YES的controller上,并且这个Controller包含在一个navigation controller中,这种情况下会设置在top & bottom上 adjustedContentInset = safeAreaInset + contentInset不管是否滚动。其他情况下与UIScrollViewContentInsetAdjustmentScrollableAxes相同

2.UIScrollViewContentInsetAdjustmentScrollableAxes: 在可滚动方向上adjustedContentInset = safeAreaInset + contentInset,在不可滚动方向上adjustedContentInset = contentInset;依赖于scrollEnabled和alwaysBounceHorizontal / vertical = YES,scrollEnabled默认为yes,所以大多数情况下,计算方式还是adjustedContentInset = safeAreaInset + contentInset

3.UIScrollViewContentInsetAdjustmentNever: adjustedContentInset = contentInset

4.UIScrollViewContentInsetAdjustmentAlways: adjustedContentInset = safeAreaInset + contentInset

当contentInsetAdjustmentBehavior设置为UIScrollViewContentInsetAdjustmentNever的时候,adjustContentInset值不受SafeAreaInset值的影响。

三、什么情况下的tableView会发生上述问题

如果设置了automaticallyAdjustsScrollViewInsets = YES,那么不会发生问题,一直都是由系统来调整内容的偏移量。

接下来排查下自己的项目中哪些页面会发生以上问题。

当tableView的frame超出安全区域范围时,系统会自动调整内容的位置,SafeAreaInsets值会不为0,于是影响tableView的adjustContentInset值,于是影响tableView的内容展示,导致tableView的content下移了SafeAreaInsets的距离。SafeAreaInsets值为0时,是正常的情况。

需要了解每个页面的结构,看tableView是否被系统的statusbar或navigationbar覆盖,如果被覆盖的话,则会发生下移。也可以通过tableview.safeAreaInsets的值来确认是因为安全区域的问题导致的内容下移。

如下代码片段,可以看出系统对tableView向下调整了20pt的距离,因为tableView超出了安全区域范围,被statusbar覆盖。

tableview.contentInset: {64, 0, 60, 0}tableview.safeAreaInsets: {20, 0, 0, 0}tableview.adjustedContentInset: {84, 0, 60, 0}

四、这个问题的解决方法有哪些?

1. 重新设置tableView的contentInset值,来抵消掉SafeAreaInset值,因为内容偏移量 = contentInset + SafeAreaInset;

如果之前自己设置了contentInset值为(64,0,0,0),现在系统又设置了SafeAreaInsets值为(64,0,0,0),那么tableView内容下移了64pt,这种情况下,可以设置contentInset值为(0,0,0,0),也就是遵从系统的设置了。

2. 设置tableView的contentInsetAdjustmentBehavior属性

如果不需要系统为你设置边缘距离,可以做以下设置:

 //如果iOS的系统是11.0,会有这样一个宏定义“#define __IPHONE_11_0  110000”;如果系统版本低于11.0则没有这个宏定义#ifdef __IPHONE_11_0   if ([tableView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {    tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;}#endif

contentInsetAdjustmentBehavior属性也是用来取代automaticallyAdjustsScrollViewInsets属性的,推荐使用这种方式。

3. 通过设置iOS 11新增的属性addtionalSafeAreaInset;

iOS 11之前,大家是通过将Controller的automaticallyAdjustsScrollViewInsets属性设置为NO,来禁止系统对tableView调整contentInsets的。如果还是想从Controller级别解决问题,那么可以通过设置Controller的additionalSafeAreaInsets属性,如果SafeAreaInset值为(20,0,0,0),那么设置additionalSafeAreaInsets属性值为(-20,0,0,0),则SafeAreaInsets不会对adjustedContentInset值产生影响,tableView内容不会显示异常。这里需要注意的是addtionalSafeAreaInset是Controller的属性,要知道SafeAreaInset的值是由哪个Controller引起的,可能是由自己的Controller调整的,可能是navigationController调整的。是由哪个Controller调整的,则设置哪个Controller的addtionalSafeAreaInset值来抵消掉SafeAreaInset值。

五、遇到的另外一个与安全区域无关的tableView内容下移的问题

我的作品页面的tableView下移了约40pt,这里是否跟安全区域有关呢?

查了下页面结构,tableView的父视图的frame在navigationbar的bottom之下,tableView在父视图的安全区域内,打印出来tableView的SafeAreaInset值也是(0,0,0,0);所以不是安全区域导致的内容下移。

经过查看代码,发现tableView的style:UITableViewStyleGrouped类型,默认tableView开头和结尾是有间距的,不需要这个间距的话,可以通过实现heightForHeaderInSection方法(返回一个较小值:0.1)和viewForHeaderInSection(返回一个view)来去除头部的留白,底部同理。

iOS 11上发生tableView顶部有留白,原因是代码中只实现了heightForHeaderInSection方法,而没有实现viewForHeaderInSection方法。那样写是不规范的,只实现高度,而没有实现view,但代码这样写在iOS 11之前是没有问题的,iOS 11之后应该是由于开启了估算行高机制引起了bug。添加上viewForHeaderInSection方法后,问题就解决了。或者添加以下代码关闭估算行高,问题也得到解决。

self.tableView.estimatedRowHeight = 0;self.tableView.estimatedSectionHeaderHeight = 0;self.tableView.estimatedSectionFooterHeight = 0;


三.iPhoneX

LaunchImage

关于iPhoneX(我就不吐槽刘海了...),如果你的APP在iPhoneX上运行发现没有充满屏幕,上下有黑色区域,那么你应该也像我一样LaunchImage没有用storyboard而是用的Assets,解决办法如图,启动图的尺寸为1125x2436

TabBarController

因为我们的项目用了第三方的TabBarController,在iPhoneX运行,tabBar看起来怪怪的...估计作者要等到猴年马月才适配iPhoneX,项目又着急上线,就自己修改了第三方,主要是tabBar高度及tabBarItem偏移适配,iPhoneX由于底部安全区的原因UITabBar高度由49pt变成了83pt,可以通过判断机型来修改相关界面代码

1
#define kDevice_Is_iPhoneX ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(11252436), [[UIScreen mainScreen] currentMode].size) : NO)


四.彻底废弃32位APP(32-bit Apps)

(1)开发者从iOS11之后再也不用学习32位的APP开发了


(2)如果从iTunes同步32位的APP会留下一个占位符APP(言外之意就是在iOS11后如果通过iTunes同步一个32位的APP到你的iOS社会只会留下一个空白的APP,无法运行)


(3)如果在Xcode的target中勾选系统版本为iOS10或者之前的系统,将会继续兼容32位APP(再次强调只是iOS11不支持32位而已,iOS10及以下还是继续兼容)从iOS11开始,32位的APP正式退出历史舞台

2.3D Touch

(1)对于能识别到是电话号码,地址等能够响应系统事件的文本对象(比如点击号码自动拨号),3D Touch不生效
 

3. 第三方应用 (3rd Party Apps)

这里比较多,对开发而言意义不大,我就捡几条重点的说了

(1)使用Facebook Messenger之前必须要安装Facebook应用(国内扑街)


(2)最重要:第三方键盘如果没有选择允许完全访问有可能会出现崩溃问题(iOS10之后苹果支持第三方键盘,具体设置在通用——键盘选项中)


(3)如果是Skyce通过可能会让音频播放掉线(静音)


4.ARKit框架

ARKit是iOS11的最大亮点,笔者将会在后续同步刷新ARKit框架开发教程

(1)两件事,一是在调试ARSession的时候如果打短信的时候就会导致出现VIO 断点的情况(就是访问硬件内存泄露的一种崩溃断点,不打的话程序就不会有问题) 二是在AR增强现实中的锚点是不可见的(理解比较困难,笔者将会在后续更新ARKit框架学习和开发博客供读者学习交流)


(2)ARSessionConfiguration(该类主要负责跟踪AR设备方向的一些高级配置)类只有在A9芯片设备(iPhone6s)及iOS11系统之后才会生效


(3)ARWorldTrackingSessionConfiguration(该类负责:配置跟踪设备的方向和位置,以及检测设备摄像头看到的现实世界的表面)也是要A9芯片及以后,同上


5.AVFoundation

AVFoundation更新比较多,但是设计的内容比较少,主要是在自定义相机环节开始全面支持双摄像头物理变焦的摄像头捕捉到的高清图像(为即将要发布的iPhone8做准备,其实这个对象在iOS10已经出来,当时只有7plus才有双摄像头所以开发中用的极少)
这里笔者就不一一翻译了,就是自定义相机中几个iOS10新增的API以前返回的nil现在做了优化

7.日历框架(EventKit)

(1)将数据存储到一个非默认的响应事件可能会失败

8.Foundation框架

主要更新在网络请求URLSessionTask这一部分

第三点相当牛逼,网络请求能够等待安全网络才请求,这对于开发而言意义重大。笔者将会在后期上代码测试 

(1)比较长,大概的意思就是加了一些API,支持应用程序退后后台时仍然能与你的Watch(苹果手表)通讯。(苹果手表网络请求依赖于手机连接的wifi)


(2)获取网络下载进度建议使用新属性


(3)添加了一个更加灵活的网络请求API,它能够等待直到你的手机连接上了安全的网络,如果是不安全的网络则该网络请求不会调用


(4)添加了APP应用程序加载网络时的一些数据编码支持。(对开发意义不大,后面一大堆是举例说明,br指的是web前端的换行标签)


最后一个环节是iOS开发中一半以上代码都出自这个框架,大家猜到了么? 不错!就是UIKit框架

 

UIKit框架在iOS11中解决了一个UITableView问题,新增了一堆代码(主要与新功能文件管理有关) 

9.UIKit框架

本次iOS11系统更新UIKit较多,主要是因为iOS11新增的文件管理功能并没有提供单独的框架,而是被直接继承在UIKit框架中,测试版问题比较多,实际还是以正式版为准
关于iOS11文件管理功能的开发,笔者将会后续提供教程学习交流 此次iOS11关于UITableView的优化也比较多,笔者将会在后续深入研究探索供大家学习交流

问题修复

设置UITableView的delaysContentTouches延迟响应属性为NO,再也不会立即触发cell的响应事件(delaysContentTouches是UIScrollerView的属性,叫做延迟响应处理。默认为YES延迟处理,作用就是当点击scrollerview中的按钮时不会被系统判定为ScrollerView的滚动手势,对这个属性不了解的小白们可以百度哈,这里笔者不再详细累述)

新的变化

(1)UIDocumentBrowserViewController(显示沙盒文件浏览器)不会显示UIDocumentPickerExtensionViewController列表中的应用程序扩展来源。虽然这些会在将在的测试版中重新启用,请考虑使用一个NSFileProviderExtension相反,因为它们是集成在UIDocumentBrowserViewController而不是在一个单独的表


(2)当拖动文件超过5项,文件移动/取消动画使用一个默认的动画。前五个文件夹系统调用移动/取消预览视图,即使它们没有使用。在后面的测试,系统不会让这些调用,而不是为动画提供了一个额外的API与项目的自定义动画不能应用。


(3)使用UITargetedDragPreview和UIDragPreview而不是UIURLDragPreviewView,因为在下一个版本中这个API将会被移除(移除并不意味着消失,有可能在测试版中开发,正式版中成了私有API)

 

(4)在storyboard或者xib中,Xcode9之后的tableview显示cell和header和footer将会更加的区分明显(实际上就是storyboard的显示细节小优化)

 

(5)在iOS11中UITableView有了一个新的系统手势可以快速选择行:两根手指快速的轻击cell,可以同时选中两个cell进入编辑状态。如果两个手指存在不同步问题,则会默认识别其中的一个手指表示单选cell

 

(6)UITableView新增了一个属性separatorInsetReference,作用是可以自定义一个cell分割线的边距


(7)iOS11中,UITableView的cell或者表头表尾默认采用自适应高度的方案,当然如果之前的tableview不想使用这个功能可以直接禁掉,或者在自适应高度代理中返回0即可


(8)当UITableViewCell的内容(主要是文本)高度过高时,可以通过设置UITableViewCell的numberOfLines属性来实现类似于UILabel一样的高度自适应变化

// tableView 偏移20/64适配
if (@available(iOS 11.0, *)) {
    self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;//UIScrollView也适用
}else {
    self.automaticallyAdjustsScrollViewInsets = NO;
}


// tableView 如果是Gruop类型的话,section之间的间距变宽,执行返回高度的同时还需要执行return UIView的代理
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
    return 10;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
    return 0.1;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    return [[UIView alloc] init];
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
    return [[UIView alloc] init];
}

//我直接贴适配代码,因为低版本直接用contentInsetAdjustmentBehavior会报警告,所有定义了如下的宏


#define  adjustsScrollViewInsets(scrollView)\

do {\

_Pragma("clang diagnostic push")\

_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")\

if ([scrollView respondsToSelector:NSSelectorFromString(@"setContentInsetAdjustmentBehavior:")]) {\

NSMethodSignature *signature = [UIScrollView instanceMethodSignatureForSelector:@selector(setContentInsetAdjustmentBehavior:)];\

NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];\

NSInteger argument = 2;\

invocation.target = scrollView;\

invocation.selector = @selector(setContentInsetAdjustmentBehavior:);\

[invocation setArgument:&argument atIndex:2];\

[invocation retainArguments];\

[invocation invoke];\

}\

_Pragma("clang diagnostic pop")\

} while (0)



#define kDevice_Is_iPhoneX ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125,2436), [[UIScreen mainScreen] currentMode].size) : NO)