iOS 11新特性与适配
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
结构的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本地化语言才能看到效果
1.2. 安全区域
在iOS 11中新增了安全区域的概念,目的是告诉开发者在这个区域下绘制的内容的显示才是有效的,否则会存在被遮挡的情况(特别是iPhoneX那帅气的刘海)。在UIView中新增safeAreaLayoutGuide
和safeAreaInsets
来获取屏幕的安全区域(对于frame布局时是很有用的)。如图所示:
举个例子,在一个空白的UIViewController
中,分别在viewDidLoad
和viewDidAppear
方法中输出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
的值是相同的。
如果你想准确地知道安全区域是什么时候被改变的,可以重写UIView
的safeAreaInsetsDidChange
方法,在这个方法里面可以监听安全区域的边距调整的事件(如果使用的是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中使用该方法来自动调整UIScrollView
的contentInset
。在iOS 11之后将会使用UIScrollView
的contentInsetAdjustmentBehavior
属性来代替该方法。
2.1.2. topLayoutGuide
和bottomLayoutGuide
属性
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. systemMinimumLayoutMargins
和viewRespectsSystemMinimumLayoutMargins
属性
该属性表示了一个系统最小的边距信息,所有的视图排版都应该遵循这个边距信息的。除非将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 控制大标题的显示
如果你想控制每个视图的大标题是否显示,这需要使用UINavigationItem
的largeTitleDisplayMode
属性来控制大标题的显示。该属性为枚举类型,定义如下:
typedef NS_ENUM(NSInteger, UINavigationItemLargeTitleDisplayMode){ /// 自动模式,会继承前一个NavigationItem所设置的模式UINavigationItemLargeTitleDisplayModeAutomatic,/// 当前 Navigationitem 总是启用大标题模式UINavigationItemLargeTitleDisplayModeAlways,/// 当前 Navigationitem 总是禁用大标题模式UINavigationItemLargeTitleDisplayModeNever,
}
根据上面的描述,可以在VC初始化init
或者awakeFromNib
方法中设置显示图标模式:
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAlways;
4.2 控制搜索控制器
iOS 11 中新增了两个属性searchController
和hidesSearchBarWhenScrolling
。这两个属性主要用于简化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,
}
其中UIScrollViewContentInsetAdjustmentAutomatic
与UIScrollViewContentInsetAdjustmentScrollableAxes
一样,ScrollView会自动计算和适应顶部和底部的内边距并且在scrollView 不可滚动时,也会设置内边距;UIScrollViewContentInsetAdjustmentNever
表示不计算内边距;UIScrollViewContentInsetAdjustmentAlways
则根据视图的安全区域来计算内边距。
如果需要感知adjustedContentInset
的变化,然后根据变化进行不同操作则可以通过重写新增的adjustedContentInsetDidChange
方法或者实现UIScrollViewDelegate
中的scrollViewDidChangeAdjustedContentInset
方法来实现。如:
//重写方法
- (void)adjustedContentInsetDidChange
{[super adjustedContentInsetDidChange];//执行操作...
}//实现委托
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView
{//执行操作...
}
除了新增上述所说的边距相关属性外,还新增了contentLayoutGuide
和frameLayoutGuide
属性,用于描述内容布局和整体布局信息。
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中如果调用UIButton
的setImage
或者setBackgrounImage
方法,如果图片的尺寸大于按钮尺寸时则会被进行拉伸。如下图:
对于上面问题,可以通过对按钮的宽度和高度进行约束来控制图标的大小。处理代码如下:
if (@available(iOS 11.0, *))
{NSLayoutConstraint *constraint = [btn.widthAnchor constraintEqualToConstant:35];constraint.active = YES;constraint = [btn.heightAnchor constraintEqualToConstant:35];constraint.active = YES;
}
调整后,图标显示正常:
注:widthAnchor
和heightAnchor
是iOS9之后增加的
持续更新
先写到这,其他同学可以针对iOS 11的问题进行提问,我会根据实际情况来补充文档并回答各位的问题。
iOS 11新特性与适配相关推荐
- 玩转iOS开发:iOS 11 新特性《UIKit新特性的基本认识》
文章分享至我的个人技术博客: https://cainluo.github.io/15099354591154.html 前两篇, 我们讲了Xcode 9的一些新特性, 可以更加方便我们去写" ...
- [IOS]IOS10新特性以及适配点(转)
iOS 10 新特性以及适配点 SiriKit 所有第三方应用都可以用Siri,支持音频.视频.消息发送接收.搜索照片.预订行程.管理锻炼等 Proactive Suggestions 系统预先建议 ...
- [翻译]Adobe Flash Player 11新特性
Adobe Flash Player 11带来了很多改进,您将在网络上看到更多精彩的内容.就像当年Flash Player 7改变了网络视频,ActionScript 3改变了开发Flash内容的方式 ...
- 十分钟接入iOS 12新特性——Siri Shortcuts
前言 Xcode 10已经正式发布,开发者可以接入Siri Shortcuts的iOS 12新特性. WWDC2018的Introduction to Siri Shortcuts Session介绍 ...
- IntelliJ IDEA 使用 Java 11新特性以及Java 8 以来的Java 11新特性介绍
文章目录 Java 11 安装 IDEA 设置 特性1:lambda表达式中允许使用var 特性2: String新增REPEAT 方法,方便拷贝字符串 特性3: 文件读写更方便:readString ...
- iOS 9应用开发教程之iOS 9新特性
iOS 9应用开发教程之iOS 9新特性 iOS 9开发概述 iOS 9是目前苹果公司用于苹果手机和苹果平板电脑的最新的操作系统.该操作系统于2015年6月8号(美国时间)被发布.本章将主要讲解iOS ...
- Java 11 新特性
2019独角兽企业重金招聘Python工程师标准>>> Java 11 新特性 转载于:https://my.oschina.net/u/3764794/blog/2993127
- C++11新特性中的匿名函数Lambda表达式的汇编实现分析(二)
2019独角兽企业重金招聘Python工程师标准>>> C++11新特性中的匿名函数Lambda表达式的汇编实现分析(一) 首先,让我们来看看以&方式进行变量捕获,同样没有参 ...
- C++11 新特性之std::thread
C++11 新特性之std::thread 原文:https://blog.csdn.net/oyoung_2012/article/details/78958274 从C++11开始,C++标准库已 ...
最新文章
- css中的display属性之li元素
- html作业三,3.15作业
- javaMP3转pcm 百度语音识别
- opencv学习笔记(二):基于肤色的人手检测
- Metro UI 菜单(Winform)
- 幼儿园计算机教师论文,幼儿园中班教师论文
- linux14.10硬盘安装,Windows 10下硬盘安装Ubuntu 14.10图文教程
- 网络配置_CentOS8 网络配置
- 狗狗币协议发布新版本Dogecoin Core 1.14.3
- PS Photoshop 无法识别数位板 钢笔压力 感叹号
- 2020神舟几号发射_神舟九号和神舟十号是什么时候发射的,宇航员分别是谁?...
- OpwnWrt 路由器MWAN3多线多拨实现方法
- ffmpeg中h264_mp4toannexb使用说明及注意事项
- 新评论接口——京东评论接口
- 谈谈我在企业内部的创业经历
- ZYNQ7010 CAN的官方例程改为XCANPS_MODE_NORMAL模式,程序没跑通
- 计算机说课比赛稿,《从军行》说课比赛稿
- SimpleFOC之ESP32(十)—— ESP-NOW和力矩反馈
- JVM学习笔记【2】 类加载执行子系统
- 投资银行业务财务报表分析
热门文章
- java jstack 死锁_用jstack找死锁
- 爬取bilibili相册的图片
- 用记事本编写第一个Java程序运行时 出现报错:错误: 类helloworld是公共的, 应在名为 helloworld.java 的文件中声明 public class helloworld {
- 9.0系统登录 服务器,游戏工作室来袭!魔兽9.0经济系统与服务器负荷再次面临考验...
- Vue初学——计算属性Computed和Methods
- 国内报价-APP时间加速
- 有关小米6手机更新REC的一些问题记录
- 面试 2.求职意向、动机、公司的了解
- fastq和fasta格式转化
- Linux端口占用查询