iOS 11 新特性

iOS 11 为整个生态系统的 UI 元素带来了一种更加大胆、动态的新风格。 本文介绍了iOS11在UI方面做了哪些更新。

首先看看Xcode9.0的新面貌

1.版本库管理:

屏幕快照 2018-03-07 下午4.30.29.png

2.iOS版本判断


常用: [[UIDevice currentDevice].systemVersion floatValue] >= 6.0 (不推荐)

ios8之后的方法: if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 11, .minorVersion = 0, .patchVersion = 0}])

Xcode9及以后 : if (@available(iOS 11.0, *))

高效的iOS版本检测

3.在UIKit’s Bars中加入的新功能

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

  • 竖屏
Simulator Screen Shot - iPhone X - 2017-12-13 at 17.42.24.png
  • 横屏
Simulator Screen Shot - iPhone X - 2017-12-13 at 17.43.40.png

4.UINavigationBar:

  • 新增prefersLargeTitles: Bool属性(大标题,默认为false,当设置为true时,navigation bar会显示大标题,向上滑动页面,navigation bar 会变小直到显示成跟之前一样,同时title位置会发生变化)

navigationController?.navigationBar.prefersLargeTitles = YES;

Simulator Screen Shot - iPhone 8 - 2017-11-30 at 15.03.24.png

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

  • UIBarItem

UIBarItem是UI tab bar item和UI bar button item的父类,要想实现上面介绍的效果,只需要为UIBarItem 设置landscapeImagePhone属性,在storyboard中也支持这个设置,对于HUD的image需要设置另一个iOS11新增的属性:largeContentSizeImage

5.UIView

更加方便的RTL边距设置
在之前的系统中我们会使用layoutMargins来获取和设置控件显示内容部分的边缘与控件边缘的距离。在iOS 11中,新增directionalLayoutMargins属性来指定边距。这两个属性的结构定义如下:

typedef struct UIEdgeInsets {
    CGFloat top, left, bottom, right;
} UIEdgeInsets;
typedef struct NSDirectionalEdgeInsets {
    CGFloat top, leading, bottom, trailing; 
} NSDirectionalEdgeInsets

从结构上看主要是将UIEdgeInsets结构的left和right调整为NSDirectionalEdgeInsets结构的leading和trailing。这一调整主要是为了Right To Left(RTL)语言下可以进行自动适配,例如:要实现文本每行尾部边距设置为30px,在以前做法则需要判断语言来区分哪些是RTL语言,然后再做设置,如:

if ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.view.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft)
{
    // Right to left 语言下每行尾部在左边
    self.view.layoutMargins.left = 30;
}
else
{
    self.view.layoutMargins.right = 30;
}

iOS 11 后则可以一步到位,如:

self.view.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(0, 0, 0, 30);

注:测试时需要添加RTL本地化语言才能看到效果

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

6.UINavigationItem变化

  • 控制大标题的显示
    如果你想控制每个视图的大标题是否显示,这需要使用UINavigationItem的largeTitleDisplayMode属性来控制大标题的显示。该属性为枚举类型,定义如下:
typedef NS_ENUM(NSInteger, UINavigationItemLargeTitleDisplayMode)
 {  
    /// 自动模式,会继承前一个NavigationItem所设置的模式
    UINavigationItemLargeTitleDisplayModeAutomatic,
    /// 当前 Navigationitem 总是启用大标题模式
    UINavigationItemLargeTitleDisplayModeAlways,
    /// 当前 Navigationitem 总是禁用大标题模式
    UINavigationItemLargeTitleDisplayModeNever,
}

根据上面的描述,可以在VC初始化init或者awakeFromNib方法中设置显示图标模式:

self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAlways;

7.ScrollView相关控件偏移问题

ScrollView相关控件,比如tableview、webView、collectionView等控件顶部会有一定距离的偏移

if (@available(iOS 11.0, *))

{

self.hostWebView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;

}

else

{

self.automaticallyAdjustsScrollViewInsets = NO;

}


8.安全区域:

iPhone X由于状态栏高度的变化,对于状态栏高度预设值的视图需要,动态获取,由于新增底部功能区域,需要对视图高度进行适配:
1、原状态栏StatusBar 20pt的高度,iPhone X为44pt;

2、顶部导航高度+状态高度 64pt的高度,iPhone X为88pt的高度;

3、底部tabbar 49pt,iPhoneX多了一个底部功能区,有34pt的高度;

  • 如果你不想让safeAreaInsets影响你的视图布局,则可以将insetsLayoutMarginsFromSafeArea设置为NO,所有的视图布局将会忽略safeAreaInsets这个属性了。要注意的是,insetsLayoutMarginsFromSafeArea仅用于使用代码实现AutoLayout(如果你是使用Xib或者SB布局你的视图,那么对该属性的设置是无效的,至少我没有发现怎么可以让布局产生变化),即使该属性为NO,视图的safeAreaInsets还是一样有值,而且安全区域变更方法safeAreaInsetsDidChange一样被调用。iPhone X竖屏时占满整个屏幕的控制器的view的safeAreaInsets是(44,0,34,0),横屏是(0,44,21,44),inset后的区域正好是safeAreaLayoutGuide区域

示例代码如下:

self.view.insetsLayoutMarginsFromSafeArea = NO;
    self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
    self.tableView.backgroundColor = [UIColor orangeColor];
    self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:self.tableView];
    
        NSArray<__kindof NSLayoutConstraint *> *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[tableView]-|" options:0 metrics:nil views:@{@"tableView" : self.tableView}];
        [self.view addConstraints:constraints];

        constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[tableView]|" options:0 metrics:nil views:@{@"tableView" : self.tableView}];
        [self.view addConstraints:constraints];
        
  

safeAreaLayoutGuide是一个相对抽象的概念,为了便于理解,我们可以把safeAreaLayoutGuide看成是一个“view”,这个“view”系统自动帮我们调整它的bounds,让它不会被各种奇奇怪怪的东西挡住,包括iPhone X的刘海区域和底部的一道杠区域,可以认为在这个“view”上一定能完整显示所有内容。

效果图:
YES

YES(default).png

NO

[图片上传失败...(image-50f27c-1520411839921)]

让view可以相对某个view的safeAreaLayoutGuide做布局,从而保证view能正常、安全地显示(相对的那个view不一定要是父view)

在一种常见的使用场景里,以前我的某个view是相对于控制器的view做布局,现在是相对控制器view的safeAreaLayoutGuide做布局了

以前是这样写
[NSLayoutConstraint constraintWithItem:someView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.vc.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];

现在是这样
[NSLayoutConstraint constraintWithItem:someView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.vc.view.safeAreaLayoutGuide attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];

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

[图片上传失败...(image-f672e2-1520411839921)]

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

[图片上传失败...(image-351dae-1520411839921)]

  • 备注:safeAreaLayoutGuide:它是UIView的一个只读属性,意味着所有UIView对象都有并且是系统帮我们创建好的
    它继承UILayoutGuide,有layoutFrame意味着它能代表一块区域,它代表的区域避开了诸如导航栏、tabbar或者其他有可能挡住你这个UIView对象显示的所有父view,意味着你的view对象只要相对另一个view的safeLayoutGuide做布局就不用担心她被奇奇怪怪的东西挡住,对于控制器的view的safeAreaLayoutGuide,他的区域同样避开了statusbar或其他有可能挡住view显示的东西,我们甚至可以用控制器的additionalSafeAreaInsets属性,来额外指定inset
    如果view完全在父view的安全区域内,或者view不在视图层级或屏幕上,那么view的safeAreaLayoutGuide区域其实和view自身是一样大的,safeAreaLayoutGuide是一个相对抽象的概念,为了便于理解,我们可以把safeAreaLayoutGuide看成是一个“view”,这个“view”系统自动帮我们调整它的bounds,让它不会被各种奇奇怪怪的东西挡住,包括iPhone X的刘海区域和底部的一道杠区域,可以认为在这个“view”上一定能完整显示所有内容。safeAreaLayoutGuide是根据safeAreaInsets来调整自己的bounds的

9.UITableView 的新特性

  • 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的计算过程如下图:

[图片上传失败...(image-d60dee-1520411839921)]

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的个数

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

有以下几点需要注意:

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

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

  • 滑动操作(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

10.控制搜索控制器

iOS 11 中新增了两个属性searchController和hidesSearchBarWhenScrolling。这两个属性主要用于简化VC对UISearchController的集成以及视觉优化。其中searchController属性用于指定当前VC的一个搜索控制器。而hidesSearchBarWhenScrolling属性则用于控制当视图滚动时是否隐藏搜索栏的UI,当该值为YES时,搜索栏只有在内容视图(UIScrollView及其子类)顶部是才会显示,在滚动过程中会隐藏起来;当该值为NO时,则不受滚动影响一直显示在导航栏中。具体的代码实现如下:

- (void)awakeFromNib
{
    [super awakeFromNib];
    
    //设置SearchController到navigationItem
    self.searchController = [[UISearchController alloc] initWithSearchResultsController:self];
    self.navigationItem.searchController = self.searchController;//ios!!新增特性
    self.navigationItem.hidesSearchBarWhenScrolling = YES;//决定滑动的时候是否隐藏搜索框,ios!!新增特性
}


11.控制搜索控制器

iOS 11 中新增了两个属性searchController和hidesSearchBarWhenScrolling。这两个属性主要用于简化VC对UISearchController的集成以及视觉优化。其中searchController属性用于指定当前VC的一个搜索控制器。而hidesSearchBarWhenScrolling属性则用于控制当视图滚动时是否隐藏搜索栏的UI,当该值为YES时,搜索栏只有在内容视图(UIScrollView及其子类)顶部是才会显示,在滚动过程中会隐藏起来;当该值为NO时,则不受滚动影响一直显示在导航栏中。具体的代码实现如下:

- (void)awakeFromNib
{
    [super awakeFromNib];
    
    //设置SearchController到navigationItem
    self.searchController = [[UISearchController alloc] initWithSearchResultsController:self];
    self.navigationItem.searchController = self.searchController;
    self.navigationItem.hidesSearchBarWhenScrolling = YES;
}

12.iOS11访问权限列表

[图片上传失败...(image-b27ec-1520411839921)]

详见 Cocoa Keys

13.定位

iOS 11中原有的NSLocationAlwaysUsageDeion被降级为NSLocationWhenInUseUsageDeion;
需要在plist文件中配置NSLocationAlwaysAndWhenInUseUsageDeion,系统框才会弹出;
使用requestAlwaysAuthorization获取权限 IOS11系统弹框会把几种权限级别全部列出,供用户选择;

NSLocationUsageDescription
获取地理位置,精准推送服务
NSLocationWhenInUseUsageDescription
获取地理位置,精准推送服务
NSLocationAlwaysUsageDescription
App需要您的同意,才能始终访问位置
NSLocationAlwaysAndWhenInUseUsageDeion
App需要您的同意,才能始终访问位置

14.Masonry

  • 支持SafeArea需要更新到V1.1.0版本
[contentView mas_makeConstraints:^(MASConstraintMaker *make) {
if (@available(iOS 11.0, *)) {
    make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
    make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom);
    make.left.equalTo(self.view.mas_safeAreaLayoutGuideLeft);
    make.right.equalTo(self.view.mas_safeAreaLayoutGuideRight);
}else{
    make.top.equalTo(self.mas_topLayoutGuideBottom);
    make.bottom.equalTo(self.mas_bottomLayoutGuideTop);
    make.left.equalTo(self.view);
    make.right.equalTo(self.view);
}
}];

15.无线部署调试

从Xcode9支持无线部署调试来看,千呼万唤的iPhone8支持无线充电几乎是必然。

下面简单梳理下无线调试的步骤:

1、第一次部署调试还是需要连线,连接好之后,选择Window->Devices and Simulators,或者直接快捷键shift+command+2;

2、保证mac与手机在同一个局域网下,在弹出的界面中,勾选connect via network;

3、勾选好后,耐心等待......连接成功3

4、当出现上图中的小球时,说明你的iPhone与Xcode匹配成功,此时,拔掉数据线即可。

5、直接运行项目测试。