- 1.UIView的变化
- 2.UIViewController
- 3.UINavigationBar
- 4.UINavigationItem
- 5. UIScrollView变化
- 6. UI主线程操作日志提醒
1.UIView的变化
NSDirectionalEdgeInsets
在iOS 11中,新增directionalLayoutMargins
属性来指定边距。
typedef struct UIEdgeInsets {
CGFloat top, left, bottom, right; // specify amount to inset (positive) for each of the edges. values can be negative to 'outset'
} UIEdgeInsets;
/* Specifically for use in methods and functions supporting user interface layout direction
*/
typedef struct NSDirectionalEdgeInsets {
CGFloat top, leading, bottom, trailing; // specify amount to inset (positive) for each of the edges. values can be negative to 'outset'
} NSDirectionalEdgeInsets API_AVAILABLE(ios(11.0),tvos(11.0),watchos(4.0));
在以前做法则需要判断语言来区分哪些是RTL语言,然后再做设置:
if ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.view.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft) {
self.view.layoutMargins.left = 10;
} else {
self.view.layoutMargins.right = 10;
}
现在实现同样的效果只需:
self.view.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(0, 0, 0, 10);
SafeArea
介绍
@property (nonatomic) BOOL insetsLayoutMarginsFromSafeArea API_AVAILABLE(ios(11.0),tvos(11.0)); // Default: YES
@property (nonatomic,readonly) UIEdgeInsets safeAreaInsets API_AVAILABLE(ios(11.0),tvos(11.0));
- (void)safeAreaInsetsDidChange API_AVAILABLE(ios(11.0),tvos(11.0));
/* The top of the safeAreaLayoutGuide indicates the unobscured top edge of the view (e.g, not behind
the status bar or navigation bar, if present). Similarly for the other edges.
*/
@property(nonatomic,readonly,strong) UILayoutGuide *safeAreaLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));
看一下安全区域的变化
- (void)viewDidLoad {
[super viewDidLoad];
NSString *safeAreaInsetsStr = NSStringFromUIEdgeInsets(self.view.safeAreaInsets);
NSString *layoutFrameStr = NSStringFromCGRect(self.view.safeAreaLayoutGuide.layoutFrame);
NSLog(@"viewDidLoad safeAreaInsets = %@, layoutFrame = %@", safeAreaInsetsStr, layoutFrameStr);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSString *safeAreaInsetsStr = NSStringFromUIEdgeInsets(self.view.safeAreaInsets);
NSString *layoutFrameStr = NSStringFromCGRect(self.view.safeAreaLayoutGuide.layoutFrame);
NSLog(@"viewDidLoad safeAreaInsets = %@, layoutFrame = %@", safeAreaInsetsStr, layoutFrameStr);
}
可以看到
2017-10-31 14:36:07.675737+0800 test[8453:648004] viewDidLoad safeAreaInsets = {0, 0, 0, 0}, layoutFrame = { {0, 0}, {375, 812} }
2017-10-31 14:36:07.702525+0800 test[8453:648004] viewDidLoad safeAreaInsets = {44, 0, 34, 0}, layoutFrame = { {0, 44}, {375, 734} }
如果安全区域改变了会掉用这个方法。
- (void)safeAreaInsetsDidChange {
//写入变更安全区域后的代码...
}
如果你不想让safeAreaInsets
影响你的视图布局,则可以将insetsLayoutMarginsFromSafeArea
设置为NO,所有的视图布局将会忽略safeAreaInsets
这个属性了。要注意的是,insetsLayoutMarginsFromSafeArea
仅用于AutoLayout,即使该属性为NO,视图的safeAreaInsets
还是一样有值,而且安全区域变更方法safeAreaInsetsDidChange
一样被调用。
使用
// 一些宏定义
// iPhone X
#define CYPHOTOLIB_IPHONEX (CYPHOTOLIB_SCREEN_W == 375.f && CYPHOTOLIB_SCREEN_H == 812.f ? YES : NO)
// Status bar height.
#define CYPHOTOLIB_STATUS_H (CYPHOTOLIB_IPHONEX ? 44.f : 20.f)
#define CYPHOTOLIB_TABBAR_H (CYPHOTOLIB_IPHONEX ? (49.f+34.f) : 49.f)
#define CYPHOTOLIB_NAVBAR_H (CYPHOTOLIB_IPHONEX ? 88.f : 64.f)
#define CYPHOTOLIB_NAVBAR_H_NoStatus 44.f
#define CYPHOTOLIB_TabbarSafeBottomMargin (CYPHOTOLIB_IPHONEX ? 34.f : 0.f)
#define CYPHOTOLIB_ViewSafeAreInsets(view) ({UIEdgeInsets insets; if(@available(iOS 11.0, *)) {insets = view.safeAreaInsets;} else {insets = UIEdgeInsetsZero;} insets;})
我们可以这么使用
// 获取safeArea
UIEdgeInsets insets = CYPHOTOLIB_ViewSafeAreInsets(self.view);
// 根据top left bottom right 的值来适配 比如横屏时iPhoneX的刘海会挡住一部分视频
// ...
2.UIViewController
过时的API
automaticallyAdjustsScrollViewInsets
iOS 7中使用该方法来自动调整UIScrollView
的contentInset
。在iOS 11之后将会使用UIScrollView
的contentInsetAdjustmentBehavior
属性来代替该方法。
topLayoutGuide
和bottomLayoutGuide
属性
在iOS 11之后将使用安全区域(Safe Area)来代替该部分功能的实现。
additionalSafeAreaInsets
属性
iOS 11加入安全区域后,对于VC则可以通过该属性来对该区域附加一个边距信息。如:
self.additionalSafeAreaInsets = UIEdgeInsetsMake(30, 0, 0, 30);
注意:这里是附加边距,意思就是在原有的safeAreaInsets
值中增加对应的边距值。如果原来的是{10, 0, 0, 10}, 则最后得出的边距是{40, 0, 0, 40}。
systemMinimumLayoutMargins
和viewRespectsSystemMinimumLayoutMargins
属性
该属性表示了一个系统最小的边距信息,所有的视图排版都应该遵循这个边距信息的。除非将viewRespectsSystemMinimumLayoutMargins
设置为NO。
viewLayoutMarginsDidChange
方法
根视图的边距变更时会触发该方法的回调。可以通过该方法来处理当边距改变时子视图的布局。
viewSafeAreaInsetsDidChange
方法
当视图的安全区域发生变更时会触发该方法的回调。可以通过该方法来处理安全区域变更时的子视图布局。
3.UINavigationBar
iOS 11中加入了大标题模式,实现该效果需要将导航栏的prefersLargeTitles
设置为YES
self.navigationController.navigationBar.prefersLargeTitles = YES;
4.UINavigationItem
控制大标题的显示
如果你想控制每个视图的大标题是否显示,这需要使用UINavigationItem
的largeTitleDisplayMode
属性来控制大标题的显示。该属性为枚举类型,定义如下:
typedef NS_ENUM(NSInteger, UINavigationItemLargeTitleDisplayMode)
{
/// 自动模式,会继承前一个NavigationItem所设置的模式
UINavigationItemLargeTitleDisplayModeAutomatic,
/// 当前 Navigationitem 总是启用大标题模式
UINavigationItemLargeTitleDisplayModeAlways,
/// 当前 Navigationitem 总是禁用大标题模式
UINavigationItemLargeTitleDisplayModeNever,
}
根据上面的描述,可以在VC初始化init
或者awakeFromNib
方法中设置显示图标模式:
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAlways;
UISearchController
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;
}
5. UIScrollView变化
之前的系统中,如果你的滚动视图包含在一个导航控制器下,系统会自动地调整你的滚动视图的contentInset
。而iOS 11新增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.testScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.testScrollView];
NSLog(@"self.testScrollView.adjustedContentInset = %@", NSStringFromUIEdgeInsets(self.testScrollView.adjustedContentInset));
});
Log中会出现下面提示:
=================================================================
Main Thread Checker: UI API called on a background thread: -[UIView bounds]
PID: 8673, TID: 692873, Thread name: (none), Queue name: com.apple.root.default-qos, QoS: 21
Backtrace:
4 test 0x00000001036e42a6 __29-[ViewController viewDidLoad]_block_invoke + 150
5 libdispatch.dylib 0x00000001084903f7 _dispatch_call_block_and_release + 12
6 libdispatch.dylib 0x000000010849143c _dispatch_client_callout + 8
7 libdispatch.dylib 0x0000000108496352 _dispatch_queue_override_invoke + 1458
8 libdispatch.dylib 0x000000010849d1f9 _dispatch_root_queue_drain + 772
9 libdispatch.dylib 0x000000010849ce97 _dispatch_worker_thread3 + 132
10 libsystem_pthread.dylib 0x00000001089511ca _pthread_wqthread + 1387
11 libsystem_pthread.dylib 0x0000000108950c4d start_wqthread + 13
2017-10-31 15:16:36.339784+0800 test[8673:692873] [reports] Main Thread Checker: UI API called on a background thread: -[UIView bounds]
PID: 8673, TID: 692873, Thread name: (none), Queue name: com.apple.root.default-qos, QoS: 21
Backtrace:
4 test 0x00000001036e42a6 __29-[ViewController viewDidLoad]_block_invoke + 150
5 libdispatch.dylib 0x00000001084903f7 _dispatch_call_block_and_release + 12
6 libdispatch.dylib 0x000000010849143c _dispatch_client_callout + 8
7 libdispatch.dylib 0x0000000108496352 _dispatch_queue_override_invoke + 1458
8 libdispatch.dylib 0x000000010849d1f9 _dispatch_root_queue_drain + 772
9 libdispatch.dylib 0x000000010849ce97 _dispatch_worker_thread3 + 132
10 libsystem_pthread.dylib 0x00000001089511ca _pthread_wqthread + 1387
11 libsystem_pthread.dylib 0x0000000108950c4d start_wqthread + 13
=================================================================
Main Thread Checker: UI API called on a background thread: -[UIScrollView initWithFrame:]
PID: 8673, TID: 692873, Thread name: (none), Queue name: com.apple.root.default-qos, QoS: 21
Backtrace:
4 test 0x00000001036e42f9 __29-[ViewController viewDidLoad]_block_invoke + 233
5 libdispatch.dylib 0x00000001084903f7 _dispatch_call_block_and_release + 12
6 libdispatch.dylib 0x000000010849143c _dispatch_client_callout + 8
7 libdispatch.dylib 0x0000000108496352 _dispatch_queue_override_invoke + 1458
8 libdispatch.dylib 0x000000010849d1f9 _dispatch_root_queue_drain + 772
9 libdispatch.dylib 0x000000010849ce97 _dispatch_worker_thread3 + 132
10 libsystem_pthread.dylib 0x00000001089511ca _pthread_wqthread + 1387
11 libsystem_pthread.dylib 0x0000000108950c4d start_wqthread + 13
2017-10-31 15:16:36.524768+0800 test[8673:692873] [reports] Main Thread Checker: UI API called on a background thread: -[UIScrollView initWithFrame:]
PID: 8673, TID: 692873, Thread name: (none), Queue name: com.apple.root.default-qos, QoS: 21
Backtrace:
4 test 0x00000001036e42f9 __29-[ViewController viewDidLoad]_block_invoke + 233
5 libdispatch.dylib 0x00000001084903f7 _dispatch_call_block_and_release + 12
6 libdispatch.dylib 0x000000010849143c _dispatch_client_callout + 8
7 libdispatch.dylib 0x0000000108496352 _dispatch_queue_override_invoke + 1458
8 libdispatch.dylib 0x000000010849d1f9 _dispatch_root_queue_drain + 772
9 libdispatch.dylib 0x000000010849ce97 _dispatch_worker_thread3 + 132
10 libsystem_pthread.dylib 0x00000001089511ca _pthread_wqthread + 1387
11 libsystem_pthread.dylib 0x0000000108950c4d start_wqthread + 13
2017-10-31 15:16:36.620180+0800 test[8673:692873] [Assert] Cannot be called with asCopy = NO on non-main thread.
2017-10-31 15:16:36.655489+0800 test[8673:692873] [Assert] Cannot be called with asCopy = NO on non-main thread.
2017-10-31 15:16:36.657453+0800 test[8673:692873] [Assert] Cannot be called with asCopy = NO on non-main thread.
2017-10-31 15:16:36.660441+0800 test[8673:692873] self.testScrollView.adjustedContentInset = {0, 0, 0, 0}
从日志中了解到一个Main Thread Checker的东西,根据苹果官方文档来看他是作用在AppKit(OSX中)、UIKit还有一些相关API上的后台线程,主要是用来监控这些框架中的接口是否在主线程中进行调用,如果没有则发出警告日志。因此,利用这个功能可以让我们快速地定位那些地方存在问题。