iOS safeAreaInsets相关知识

7,238 阅读4分钟

1: 前言

iOS 11系统发布后, UIView多了几个安全区域相关的属性和方法, 用于界面适配, 如:safeAreaInsets, safeAreaLayoutGuide,insetsLayoutMarginsFromSafeArea,以及safeAreaInsetsDidChange方法;

理解:
在iOS11前, 做界面适配时, 如果界面上有导航栏时, 如果想做到界面不被导航栏遮盖住, 需要在设置约束时, 可将frame.origin.y设置为0,如:
self.bgView.frame = CGRectMake(0, 0, UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height);
(bgView是一个控件)
当iPhoneX系列没发布前, 其他所有iPhone机型的导航栏, 状态栏, tabbar栏, 高度都是一样, 做适配的时候很简单.但是iPhoneX系列发布后, 出现了新的状态栏, tabbar栏高度, 导致适配工作量加大,所以官方新增了safeAreaInsets等属性,方便界面适配.

2: safeAreaInsets

2.1  简述safeAreaInsets的上下左右的距离

safeArea是指没有被状态栏, 导航栏, tabbar栏, toolbars, 或其他视图控制器遮盖的区域, 通过safeAreaInsets可以获取到视图的安全距离. 但是如果一个view没有在视图层次结构中或未在屏幕上显示, 则safeAreaInsets为0;

示例一: 在iPhone6s上测试一个控件的安全距离(iOS系统为12.0)
- (void)viewDidLoad {
    [super viewDidLoad];
    if (@available(iOS 11.0, *)) {//viewDidLoad
           NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
           NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
           NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
           NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
    }
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    if (@available(iOS 11.0, *)) {
        NSLog(@"2 --- viewWillAppear");
        NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
        NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
        NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
        NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
    }
}

- (void)viewSafeAreaInsetsDidChange {
    [super viewSafeAreaInsetsDidChange];
    if (@available(iOS 11.0, *)) {
        NSLog(@"3 --- viewSafeAreaInsetsDidChange");
        NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
        NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
        NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
        NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
    }
}

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    if (@available(iOS 11.0, *)) {//viewWillLayoutSubviews
        NSLog(@"4 --- viewWillLayoutSubviews");
        NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
        NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
        NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
        NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
    }
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    if (@available(iOS 11.0, *)) {//viewDidLayoutSubviews
        NSLog(@"5 --- viewDidLayoutSubviews");
        NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
        NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
        NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
        NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
    }
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    if (@available(iOS 11.0, *)) {
        NSLog(@"6 --- viewDidAppear");
        NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
        NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
        NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
        NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
    }
}


输出结果:
1 --- viewDidLoad
self.view.safeAreaInsets.top = 0.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 0.000000
self.view.safeAreaInsets.right = 0.000000

2 --- viewWillAppear
self.view.safeAreaInsets.top = 0.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 0.000000
self.view.safeAreaInsets.right = 0.000000

3 --- viewSafeAreaInsetsDidChange
self.view.safeAreaInsets.top = 64.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 49.000000
self.view.safeAreaInsets.right = 0.000000

4 --- viewWillLayoutSubviews
self.view.safeAreaInsets.top = 64.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 49.000000
self.view.safeAreaInsets.right = 0.000000

5 --- viewDidLayoutSubviews
self.view.safeAreaInsets.top = 64.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 49.000000
self.view.safeAreaInsets.right = 0.000000

6 --- viewDidAppear
self.view.safeAreaInsets.top = 64.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 49.000000
self.view.safeAreaInsets.right = 0.000000

示例二: 在iPhone6s上测试一个控件的安全距离(iOS系统为9.3)
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1 --- viewDidLoad");
    NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
    NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
    NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
    NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
}

结果: 代码直接崩溃, 报错信息:-[UIView safeAreaInsets]: unrecognized selector sent to instance 0x7fdc674467c0

示例三: 在iPhoneX上测试一个控件的安全距离
- (void)viewDidLoad {
    [super viewDidLoad];
    if (@available(iOS 11.0, *)) {
        NSLog(@"1 --- viewDidLoad");
        NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
        NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
        NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
        NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
    }
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    if (@available(iOS 11.0, *)) {
        NSLog(@"2 --- viewWillAppear");
        NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
        NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
        NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
        NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
    }
}

- (void)viewSafeAreaInsetsDidChange {
    [super viewSafeAreaInsetsDidChange];
    if (@available(iOS 11.0, *)) {
        NSLog(@"3 --- viewSafeAreaInsetsDidChange");
        NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
        NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
        NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
        NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
    }
}

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    if (@available(iOS 11.0, *)) {//viewWillLayoutSubviews
        NSLog(@"4 --- viewWillLayoutSubviews");
        NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
        NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
        NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
        NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
    }
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    if (@available(iOS 11.0, *)) {
        NSLog(@"5 --- viewDidLayoutSubviews");
        NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
        NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
        NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
        NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
    }
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    if (@available(iOS 11.0, *)) {
        NSLog(@"6 --- viewDidAppear");
        NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
        NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
        NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
        NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
    }
}

输出结果:
1 --- viewDidLoad
self.view.safeAreaInsets.top = 0.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 0.000000
self.view.safeAreaInsets.right = 0.000000

2 --- viewWillAppear
self.view.safeAreaInsets.top = 0.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 0.000000
self.view.safeAreaInsets.right = 0.000000

3 --- viewSafeAreaInsetsDidChange
self.view.safeAreaInsets.top = 88.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 83.000000
self.view.safeAreaInsets.right = 0.000000

4 --- viewWillLayoutSubviews
self.view.safeAreaInsets.top = 88.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 83.000000
self.view.safeAreaInsets.right = 0.000000

5 --- viewDidLayoutSubviews
self.view.safeAreaInsets.top = 88.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 83.000000
self.view.safeAreaInsets.right = 0.000000

6 --- viewDidAppear
self.view.safeAreaInsets.top = 88.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 83.000000
self.view.safeAreaInsets.right = 0.000000


小结:
(1)view没有在视图层次结构中或未在屏幕上显示时, safeAreaInsets为0;
(2)矩形或刘海屏手机均有安全距离, 只要iOS系统大于或等于11;
(3)如果安全距离存在, 可以通过safeAreaInsets获取到视图的安全距离;

2.2  控制器的rootView的安全区域

对于一个viewController的root view, safeArea指的是未被状态栏、一些可见的bars和通过additionalSafeAreaInsets属性设置的值遮盖的区域;

                  (图片来自于网络)

2.3   父视图下的子视图的安全距离

对于在视图层次结构中的其他视图, safeArea指的是未被状态栏、navigation bars、tabbar、toolbars或其他视图控制器遮盖的区域。 例如: 如果一个视图完全在它父视图的范围内, 那么safeAreaInsets为0;如果其超出父视图的安全范围,那么safeAreaInsets按照被遮住的大小计算。

示例一:vc的rootView是redView, redView有个subView是yellowView, 当yellowView不处于redView的安全区域之内时:

             yellowView是redView的子控件

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    if (@available(iOS 11.0, *)) {
        //打印redView的安全距离(redView就是vc的root View)
        NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
        NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
        NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
        NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
        
        //打印yellowView的安全距离
        NSLog(@"self.yellowView.safeAreaInsets.top = %f",self.yellowView.safeAreaInsets.top);
        NSLog(@"self.yellowView.safeAreaInsets.left = %f",self.yellowView.safeAreaInsets.left);
        NSLog(@"self.yellowView.safeAreaInsets.bottom = %f",self.yellowView.safeAreaInsets.bottom);
        NSLog(@"self.yellowView.safeAreaInsets.right = %f",self.yellowView.safeAreaInsets.right);
    }
}

输出结果:
self.view.safeAreaInsets.top = 44.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 34.000000
self.view.safeAreaInsets.right = 0.000000


self.yellowView.safeAreaInsets.top = 44.000000
self.yellowView.safeAreaInsets.left = 0.000000
self.yellowView.safeAreaInsets.bottom = 34.000000
self.yellowView.safeAreaInsets.right = 0.000000

从输出结果可以看出,如果子视图超出父视图的安全范围, 那么safeAreaInsets按照被遮住的大小计算;


示例二:vc的rootView是redView, redView有个subView是yellowView, 当yellowView处于redView的安全区域之内时:

             yellowView是redView的子控件

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    if (@available(iOS 11.0, *)) {
        NSLog(@"self.view.safeAreaInsets.top = %f",self.view.safeAreaInsets.top);
        NSLog(@"self.view.safeAreaInsets.left = %f",self.view.safeAreaInsets.left);
        NSLog(@"self.view.safeAreaInsets.bottom = %f",self.view.safeAreaInsets.bottom);
        NSLog(@"self.view.safeAreaInsets.right = %f",self.view.safeAreaInsets.right);
        
        NSLog(@"self.yellowView.safeAreaInsets.top = %f",self.yellowView.safeAreaInsets.top);
        NSLog(@"self.yellowView.safeAreaInsets.left = %f",self.yellowView.safeAreaInsets.left);
        NSLog(@"self.yellowView.safeAreaInsets.bottom = %f",self.yellowView.safeAreaInsets.bottom);
        NSLog(@"self.yellowView.safeAreaInsets.right = %f",self.yellowView.safeAreaInsets.right);
    }
}

输出结果:
self.view.safeAreaInsets.top = 44.000000
self.view.safeAreaInsets.left = 0.000000
self.view.safeAreaInsets.bottom = 34.000000
self.view.safeAreaInsets.right = 0.000000

self.yellowView.safeAreaInsets.top = 0.000000
self.yellowView.safeAreaInsets.left = 0.000000
self.yellowView.safeAreaInsets.bottom = 0.000000
self.yellowView.safeAreaInsets.right = 0.000000

从输出结果可以看出视图处于父视图的安全区域中时, safeAreaInsets为0;

2.4  修改安全区域

如果对系统所提供的安全区域不满意, 还可以通过additionalSafeAreaInsets属性来修改安全区域, 示例一:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = UIColor.redColor;
    //修改安全区域  
    self.additionalSafeAreaInsets = UIEdgeInsetsMake(100, 30, 100, 40);
}

输出结果:
self.view.safeAreaInsets.top = 144.000000
self.view.safeAreaInsets.left = 30.000000
self.view.safeAreaInsets.bottom = 134.000000
self.view.safeAreaInsets.right = 40.000000

从输出接口可以看出:
(1)通过additionalSafeAreaInsets可以修改安全区域的大小;
(2)修改的安全区域的大小时,是在原来的安全区域的基础上做出修改的;

由此可以看出,safeAreaInsets指的就是一个控件可见区域距离屏幕上下左右边的距离。

2.5   做屏幕适配

一个控制器从创建到界面显示, 会依次调用以下方法:
- (void)viewDidLoad;
- (void)viewWillAppear:(BOOL)animated;
- (void)viewSafeAreaInsetsDidChange;
- (void)viewWillLayoutSubviews;
- (void)viewDidLayoutSubviews;
- (void)viewDidAppear:(BOOL)animated;

在调用- viewSafeAreaInsetsDidChange方法时, 界面的safeAreaInsets值会被计算出来,在这个方法中可以更改控件的位置;

参考:www.jianshu.com/p/671e70d8d…