iOS 11新特性与适配

  • 1. UIView变化
    • 1.1. 更加方便的RTL边距设置
    • 1.2. 安全区域
  • 2. UIViewController变化
    • 2.1. 废除API
      • 2.1.1. `automaticallyAdjustsScrollViewInsets`方法
      • 2.1.2. `topLayoutGuide`和`bottomLayoutGuide`属性
    • 2.2. 排版
      • 2.2.1. `additionalSafeAreaInsets`属性
      • 2.2.2. `systemMinimumLayoutMargins`和`viewRespectsSystemMinimumLayoutMargins`属性
      • 2.2.3. `viewLayoutMarginsDidChange`方法
      • 2.2.4. `viewSafeAreaInsetsDidChange`方法
  • 3. UINavigationBar变化
  • 4. UINavigationItem变化
    • 4.1 控制大标题的显示
    • 4.2 控制搜索控制器
  • 5. UIScrollView变化
  • 6. UI主线程操作日志提醒
  • 7. 关于UIButton的设置图片变形问题
  • 持续更新

iOS 11正式发布了,下面整理了一些该版本下的特点还有如何进行兼容适配工作。

1. UIView变化

1.1. 更加方便的RTL边距设置

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

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

从结构上看主要是将UIEdgeInsets结构的leftright调整为NSDirectionalEdgeInsets结构的leadingtrailing。这一调整主要是为了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本地化语言才能看到效果

1.2. 安全区域

在iOS 11中新增了安全区域的概念,目的是告诉开发者在这个区域下绘制的内容的显示才是有效的,否则会存在被遮挡的情况(特别是iPhoneX那帅气的刘海)。在UIView中新增safeAreaLayoutGuidesafeAreaInsets来获取屏幕的安全区域(对于frame布局时是很有用的)。如图所示:

举个例子,在一个空白的UIViewController中,分别在viewDidLoadviewDidAppear方法中输出view.safeAreaInsets观察边距情况,代码如下:

- (void)viewDidLoad
{[super viewDidLoad];NSString *edgeStr = NSStringFromUIEdgeInsets(self.view.safeAreaInsets);NSString *layoutFrmStr = NSStringFromCGRect(self.view.safeAreaLayoutGuide.layoutFrame);NSLog(@"viewDidLoad safeAreaInsets = %@, layoutFrame = %@", edgeStr, layoutFrmStr);=
}- (void)viewDidAppear:(BOOL)animated
{[super viewDidAppear:animated];NSString *edgeStr = NSStringFromUIEdgeInsets(self.view.safeAreaInsets);NSString *layoutFrmStr = NSStringFromCGRect(self.view.safeAreaLayoutGuide.layoutFrame);NSLog(@"viewDidAppear safeAreaInsets = %@, layoutFrame = %@", edgeStr, layoutFrmStr);
}

可以看到其输出为:

2017-09-19 14:45:50.246095+0800 Sample[5608:1365070] viewDidLoad safeAreaInsets = {0, 0, 0, 0}, layoutFrame = {{0, 0}, {375, 667}}
2017-09-19 14:45:50.257807+0800 Sample[5608:1365070] viewDidAppear safeAreaInsets = {20, 0, 0, 0}, layoutFrame = {{0, 20}, {375, 603}}

可见,在视图显示完成的时候View的顶部边距变为了20px,而这20px正是状态栏的高度。同样原理,如果你的是一个UINavigationController那在显示的时候view.safeAreaInsets就会变成{64, 0, 0, 0}注意:在该VC下所有的UIView及其子类获取到safeAreaInsets的值是相同的。

如果你想准确地知道安全区域是什么时候被改变的,可以重写UIViewsafeAreaInsetsDidChange方法,在这个方法里面可以监听安全区域的边距调整的事件(如果使用的是UIViewController,其也提供相应方法来实现监听,下一章节会讲述该部分内容),代码如下:

- (void)safeAreaInsetsDidChange
{//写入变更安全区域后的代码...
}

如果你不想让safeAreaInsets影响你的视图布局,则可以将insetsLayoutMarginsFromSafeArea设置为NO,所有的视图布局将会忽略safeAreaInsets这个属性了。要注意的是,insetsLayoutMarginsFromSafeArea仅用于使用代码实现AutoLayout(如果你是使用Xib或者SB布局你的视图,那么对该属性的设置是无效的,至少我没有发现怎么可以让布局产生变化),即使该属性为NO,视图的safeAreaInsets还是一样有值,而且安全区域变更方法safeAreaInsetsDidChange一样被调用。可以参考下面示例代码:

@interface ViewController ()@property (nonatomic, strong) UITableView *tableView;@end@implementation ViewController- (void)viewDidLoad
{[super viewDidLoad];self.view.backgroundColor = [UIColor yellowColor];self.view.insetsLayoutMarginsFromSafeArea = NO;self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];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];
}@end

上面代码在insetsLayoutMarginsFromSafeArea属性尚未设置时其布局受SafeArea影响,效果如下:

设置后不再受SafeArea影响,效果如下:

2. UIViewController变化

2.1. 废除API

2.1.1. automaticallyAdjustsScrollViewInsets方法

iOS 7中使用该方法来自动调整UIScrollViewcontentInset。在iOS 11之后将会使用UIScrollViewcontentInsetAdjustmentBehavior属性来代替该方法。

2.1.2. topLayoutGuidebottomLayoutGuide属性

iOS 7中使用这两个属性来指导带有导航栏(NaviagtionBar)和页签栏(TabBar)的视图排版。其作用如下图所示:

在iOS 11之后将使用安全区域(Safe Area)来代替该部分功能的实现。

2.2. 排版

2.2.1. additionalSafeAreaInsets属性

iOS 11加入安全区域后,对于VC则可以通过该属性来对该区域附加一个边距信息。如:

self.additionalSafeAreaInsets = UIEdgeInsetsMake(30, 0, 0, 30);

注意:这里是附加边距,意思就是在原有的safeAreaInsets值中增加对应的边距值。如果原来的是{10, 0, 0, 10}, 则最后得出的边距是{40, 0, 0, 40}。

2.2.2. systemMinimumLayoutMarginsviewRespectsSystemMinimumLayoutMargins属性

该属性表示了一个系统最小的边距信息,所有的视图排版都应该遵循这个边距信息的。除非将viewRespectsSystemMinimumLayoutMargins设置为NO。

2.2.3. viewLayoutMarginsDidChange方法

根视图的边距变更时会触发该方法的回调。可以通过该方法来处理当边距改变时子视图的布局。

2.2.4. viewSafeAreaInsetsDidChange方法

当视图的安全区域发生变更时会触发该方法的回调。可以通过该方法来处理安全区域变更时的子视图布局。

3. UINavigationBar变化

iOS 11中加入了大标题模式,其显示效果如下所示:

实现该效果需要将导航栏的prefersLargeTitles设置为YES,如:

self.navigationController.navigationBar.prefersLargeTitles = YES;

4. UINavigationItem变化

4.1 控制大标题的显示

如果你想控制每个视图的大标题是否显示,这需要使用UINavigationItemlargeTitleDisplayMode属性来控制大标题的显示。该属性为枚举类型,定义如下:

typedef NS_ENUM(NSInteger, UINavigationItemLargeTitleDisplayMode){  /// 自动模式,会继承前一个NavigationItem所设置的模式UINavigationItemLargeTitleDisplayModeAutomatic,/// 当前 Navigationitem 总是启用大标题模式UINavigationItemLargeTitleDisplayModeAlways,/// 当前 Navigationitem 总是禁用大标题模式UINavigationItemLargeTitleDisplayModeNever,
}

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

self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAlways;

4.2 控制搜索控制器

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

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

效果如下图所示:

5. UIScrollView变化

之前的系统中,如果你的滚动视图包含在一个导航控制器下,系统会自动地调整你的滚动视图的contentInset。而iOS 11新增adjustedContentInset属性取替之前contentInset的处理方式。这两者之间的关系如下图所示:

通过一个例子来验证这说法,代码如下:

- (void)viewDidLoad
{[super viewDidLoad];NSLog(@"viewDidLoad");NSLog(@"self.tableView.contentInset = %@", NSStringFromUIEdgeInsets(self.tableView.contentInset));NSLog(@"self.tableView.adjustedContentInset = %@", NSStringFromUIEdgeInsets(self.tableView.adjustedContentInset));
}- (void)viewDidAppear:(BOOL)animated
{[super viewDidAppear:animated];NSLog(@"viewDidAppear");NSLog(@"self.tableView.contentInset = %@", NSStringFromUIEdgeInsets(self.tableView.contentInset));NSLog(@"self.tableView.adjustedContentInset = %@", NSStringFromUIEdgeInsets(self.tableView.adjustedContentInset));
}

执行后输出下面信息:

2017-09-20 11:54:09.361348+0800 Sample[1276:375286] viewDidLoad
2017-09-20 11:54:09.361432+0800 Sample[1276:375286] self.tableView.contentInset = {0, 0, 0, 0}
2017-09-20 11:54:09.361462+0800 Sample[1276:375286] self.tableView.adjustedContentInset = {0, 0, 0, 0}
2017-09-20 11:54:09.420000+0800 Sample[1276:375286] viewDidAppear
2017-09-20 11:54:09.420378+0800 Sample[1276:375286] self.tableView.contentInset = {0, 0, 0, 0}
2017-09-20 11:54:09.420554+0800 Sample[1276:375286] self.tableView.adjustedContentInset = {20, 0, 0, 0}

可见,tableView的adjustedContentInset自动改变了,但是contentInset的值是保持不变的。**注:一定要是VC的根视图为UIScrollView或者其子类才能够得到adjustedContentInset的值,否则获取到的是空值。**而且非根视图的滚动视图就会被安全区域所裁剪,看到的样式如下图所示:

通过使用contentInsetAdjustmentBehavior属性可以控制 adjustedContentInset的变化。该属性为枚举类型,其定义如下:

typedef NS_ENUM(NSInteger, UIScrollViewContentInsetAdjustmentBehavior) {UIScrollViewContentInsetAdjustmentAutomatic,UIScrollViewContentInsetAdjustmentScrollableAxes, UIScrollViewContentInsetAdjustmentNever,UIScrollViewContentInsetAdjustmentAlways,
}

其中UIScrollViewContentInsetAdjustmentAutomaticUIScrollViewContentInsetAdjustmentScrollableAxes一样,ScrollView会自动计算和适应顶部和底部的内边距并且在scrollView 不可滚动时,也会设置内边距;UIScrollViewContentInsetAdjustmentNever表示不计算内边距;UIScrollViewContentInsetAdjustmentAlways则根据视图的安全区域来计算内边距。

如果需要感知adjustedContentInset的变化,然后根据变化进行不同操作则可以通过重写新增的adjustedContentInsetDidChange方法或者实现UIScrollViewDelegate中的scrollViewDidChangeAdjustedContentInset方法来实现。如:

//重写方法
- (void)adjustedContentInsetDidChange
{[super adjustedContentInsetDidChange];//执行操作...
}//实现委托
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView
{//执行操作...
}

除了新增上述所说的边距相关属性外,还新增了contentLayoutGuideframeLayoutGuide属性,用于描述内容布局和整体布局信息。

6. UI主线程操作日志提醒

之前的系统中如果你不小心将UI放入非主线程操作时,Debug日志是没有任何信息反馈的,导致有时候在排错时非常困难。在新的Xcode 9中,如果你处于调试状态,将UI放入非主线程操作,如:

dispatch_async(dispatch_get_global_queue(0, 0), ^{self.tv = [[UITableView alloc] initWithFrame:self.view.bounds];[self.view addSubview:self.tv];NSLog(@"self.tv.adjustedContentInset = %@", NSStringFromUIEdgeInsets(self.tv.adjustedContentInset));});

Log中会出现下面提示:

=================================================================
Main Thread Checker: UI API called on a background thread: -[UIView bounds]
PID: 16919, TID: 2972321, Thread name: (none), Queue name: com.apple.root.default-qos, QoS: 21
Backtrace:
4   Sample                              0x00000001004885dc __29-[ViewController viewDidLoad]_block_invoke + 112
5   libdispatch.dylib                   0x000000010077149c _dispatch_call_block_and_release + 24
6   libdispatch.dylib                   0x000000010077145c _dispatch_client_callout + 16
7   libdispatch.dylib                   0x000000010077d56c _dispatch_queue_override_invoke + 980
8   libdispatch.dylib                   0x0000000100782b54 _dispatch_root_queue_drain + 616
9   libdispatch.dylib                   0x0000000100782880 _dispatch_worker_thread3 + 136
10  libsystem_pthread.dylib             0x000000018300b130 _pthread_wqthread + 1268
11  libsystem_pthread.dylib             0x000000018300ac30 start_wqthread + 4

从日志中了解到一个Main Thread Checker的东西,根据苹果官方文档来看他是作用在AppKit(OSX中)、UIKit还有一些相关API上的后台线程,主要是用来监控这些框架中的接口是否在主线程中进行调用,如果没有则发出警告日志。因此,利用这个功能可以让我们快速地定位那些地方存在问题。

7. 关于UIButton的设置图片变形问题

在iOS 11中如果调用UIButtonsetImage或者setBackgrounImage方法,如果图片的尺寸大于按钮尺寸时则会被进行拉伸。如下图:

对于上面问题,可以通过对按钮的宽度和高度进行约束来控制图标的大小。处理代码如下:

if (@available(iOS 11.0, *))
{NSLayoutConstraint *constraint = [btn.widthAnchor constraintEqualToConstant:35];constraint.active = YES;constraint = [btn.heightAnchor constraintEqualToConstant:35];constraint.active = YES;
}

调整后,图标显示正常:

注:widthAnchorheightAnchor是iOS9之后增加的

持续更新

先写到这,其他同学可以针对iOS 11的问题进行提问,我会根据实际情况来补充文档并回答各位的问题。

iOS 11新特性与适配相关推荐

  1. 玩转iOS开发:iOS 11 新特性《UIKit新特性的基本认识》

    文章分享至我的个人技术博客: https://cainluo.github.io/15099354591154.html 前两篇, 我们讲了Xcode 9的一些新特性, 可以更加方便我们去写" ...

  2. [IOS]IOS10新特性以及适配点(转)

    iOS 10 新特性以及适配点 SiriKit 所有第三方应用都可以用Siri,支持音频.视频.消息发送接收.搜索照片.预订行程.管理锻炼等 Proactive Suggestions 系统预先建议 ...

  3. [翻译]Adobe Flash Player 11新特性

    Adobe Flash Player 11带来了很多改进,您将在网络上看到更多精彩的内容.就像当年Flash Player 7改变了网络视频,ActionScript 3改变了开发Flash内容的方式 ...

  4. 十分钟接入iOS 12新特性——Siri Shortcuts

    前言 Xcode 10已经正式发布,开发者可以接入Siri Shortcuts的iOS 12新特性. WWDC2018的Introduction to Siri Shortcuts Session介绍 ...

  5. IntelliJ IDEA 使用 Java 11新特性以及Java 8 以来的Java 11新特性介绍

    文章目录 Java 11 安装 IDEA 设置 特性1:lambda表达式中允许使用var 特性2: String新增REPEAT 方法,方便拷贝字符串 特性3: 文件读写更方便:readString ...

  6. iOS 9应用开发教程之iOS 9新特性

    iOS 9应用开发教程之iOS 9新特性 iOS 9开发概述 iOS 9是目前苹果公司用于苹果手机和苹果平板电脑的最新的操作系统.该操作系统于2015年6月8号(美国时间)被发布.本章将主要讲解iOS ...

  7. Java 11 新特性

    2019独角兽企业重金招聘Python工程师标准>>> Java 11 新特性 转载于:https://my.oschina.net/u/3764794/blog/2993127

  8. C++11新特性中的匿名函数Lambda表达式的汇编实现分析(二)

    2019独角兽企业重金招聘Python工程师标准>>> C++11新特性中的匿名函数Lambda表达式的汇编实现分析(一) 首先,让我们来看看以&方式进行变量捕获,同样没有参 ...

  9. C++11 新特性之std::thread

    C++11 新特性之std::thread 原文:https://blog.csdn.net/oyoung_2012/article/details/78958274 从C++11开始,C++标准库已 ...

最新文章

  1. css中的display属性之li元素
  2. html作业三,3.15作业
  3. javaMP3转pcm 百度语音识别
  4. opencv学习笔记(二):基于肤色的人手检测
  5. Metro UI 菜单(Winform)
  6. 幼儿园计算机教师论文,幼儿园中班教师论文
  7. linux14.10硬盘安装,Windows 10下硬盘安装Ubuntu 14.10图文教程
  8. 网络配置_CentOS8 网络配置
  9. 狗狗币协议发布新版本Dogecoin Core 1.14.3
  10. PS Photoshop 无法识别数位板 钢笔压力 感叹号
  11. 2020神舟几号发射_神舟九号和神舟十号是什么时候发射的,宇航员分别是谁?...
  12. OpwnWrt 路由器MWAN3多线多拨实现方法
  13. ffmpeg中h264_mp4toannexb使用说明及注意事项
  14. 新评论接口——京东评论接口
  15. 谈谈我在企业内部的创业经历
  16. ZYNQ7010 CAN的官方例程改为XCANPS_MODE_NORMAL模式,程序没跑通
  17. 计算机说课比赛稿,《从军行》说课比赛稿
  18. SimpleFOC之ESP32(十)—— ESP-NOW和力矩反馈
  19. JVM学习笔记【2】 类加载执行子系统
  20. 投资银行业务财务报表分析

热门文章

  1. java jstack 死锁_用jstack找死锁
  2. 爬取bilibili相册的图片
  3. 用记事本编写第一个Java程序运行时 出现报错:错误: 类helloworld是公共的, 应在名为 helloworld.java 的文件中声明 public class helloworld {
  4. 9.0系统登录 服务器,游戏工作室来袭!魔兽9.0经济系统与服务器负荷再次面临考验...
  5. Vue初学——计算属性Computed和Methods
  6. 国内报价-APP时间加速
  7. 有关小米6手机更新REC的一些问题记录
  8. 面试 2.求职意向、动机、公司的了解
  9. fastq和fasta格式转化
  10. Linux端口占用查询