安全区域适配
一、自适应的UIView类别
使用 mas_bp_safeAreaLayoutGuideBottom
和 bp_safeAreaInsets
基本能解决iPhoneX适配问题。具体参考后面实例。
//
// UIView+SafeArea.h
//
#import <UIKit/UIKit.h>
#import <View+MASAdditions.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIView (SafeArea)
- (UIEdgeInsets)bp_safeAreaInsets;
- (UILayoutGuide *)bp_safeAreaLayoutGuide;
#pragma mark - Masonry
- (MASViewAttribute *)mas_bp_safeAreaLayoutGuide;
- (MASViewAttribute *)mas_bp_safeAreaLayoutGuideTop;
- (MASViewAttribute *)mas_bp_safeAreaLayoutGuideBottom;
- (MASViewAttribute *)mas_bp_safeAreaLayoutGuideLeft;
- (MASViewAttribute *)mas_bp_safeAreaLayoutGuideRight;
@end
NS_ASSUME_NONNULL_END
//
// UIView+SafeArea.m
//
#import "UIView+SafeArea.h"
@implementation UIView (SafeArea)
- (UIEdgeInsets)bp_safeAreaInsets {
#if defined(__IPHONE_11_0)
if (@available(iOS 11, *)) {
return self.safeAreaInsets;
}
#endif
return UIEdgeInsetsZero;
}
/// UILayoutGuide可以被添加到View中,和View一样可以设置各种约束,参与布局。但是它不会被渲染出来,不会响应事件路由。
- (UILayoutGuide *)bp_safeAreaLayoutGuide {
#if defined(__IPHONE_11_0)
if (@available(iOS 11, *)) {
return self.safeAreaLayoutGuide;
}
#endif
static UILayoutGuide *normalGuide = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
normalGuide = [[UILayoutGuide alloc] init];
[self addLayoutGuide:normalGuide];
// 约束代码,设置跟self大小完全一样
[normalGuide.topAnchor constraintEqualToAnchor:self.topAnchor constant:statusBarAndNaviBarHeight].active = YES;
[normalGuide.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES;
[normalGuide.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES;
[normalGuide.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-bottomSafeHeight].active = YES;
});
return normalGuide;
}
- (MASViewAttribute *)mas_bp_safeAreaLayoutGuide {
#if defined(__IPHONE_11_0)
if (@available(iOS 11, *)) {
return self.mas_safeAreaLayoutGuide;
}
#endif
return self.mas_bottom;
}
- (MASViewAttribute *)mas_bp_safeAreaLayoutGuideTop {
#if defined(__IPHONE_11_0)
if (@available(iOS 11, *)) {
return self.mas_safeAreaLayoutGuideTop;
}
#endif
return self.mas_top;
}
- (MASViewAttribute *)mas_bp_safeAreaLayoutGuideBottom {
#if defined(__IPHONE_11_0)
if (@available(iOS 11, *)) {
return self.mas_safeAreaLayoutGuideBottom;
}
#endif
return self.mas_bottom;
}
- (MASViewAttribute *)mas_bp_safeAreaLayoutGuideLeft {
#if defined(__IPHONE_11_0)
if (@available(iOS 11, *)) {
return self.mas_safeAreaLayoutGuideLeft;
}
#endif
return self.mas_left;
}
- (MASViewAttribute *)mas_bp_safeAreaLayoutGuideRight {
#if defined(__IPHONE_11_0)
if (@available(iOS 11, *)) {
return self.mas_safeAreaLayoutGuideRight;
}
#endif
return self.mas_right;
}
@end
二、实际使用中 若以上不能达到要求,可以监听安全区域发生变化的回调,重新设置约束
-----在控制器内------
#pragma mark - SafeAreaInsetsDidChange
#if defined(__IPHONE_11_0)
- (void)viewSafeAreaInsetsDidChange {
[super viewSafeAreaInsetsDidChange];
NSLog(@"safeAreaInset %@", NSStringFromUIEdgeInsets(self.view.bp_safeAreaInsets));
// iPhoneX系列
// 竖屏 {44, 0, 34, 0}
// 横屏 {0, 44, 21, 34}
// 其他机型
// {20, 0, 0, 0}
}
#endif
-----在UIView内------
#pragma mark - SafeAreaInsetsDidChange
#if defined(__IPHONE_11_0)
- (void)safeAreaInsetsDidChange {
[super safeAreaInsetsDidChange];
NSLog(@"safeAreaInset %@", NSStringFromUIEdgeInsets(self.bp_safeAreaInsets));
}
#endif
- 安全区域发生变化并回调 要求是在iOS11系统以上,并且回调触发在 viewDidAppear之后、layoutSubView之前,所以要注意 viewDidLoad 和 viewSafeAreaInsetsDidChange 两个地方都要设置约束。在 viewDidLoad 设置约束是为了支持 iOS11以前系统。
- 隐藏系统导航栏时: 非刘海屏,iOS11及之后系统,safeAreaInsets.top = 20 , iOS11之前系统 safeAreaInsets.top = 0 ;不隐藏系统导航栏时 都=0
实例参考
1、自定义navBar,隐藏系统导航栏 (适配顶部)
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:self.customNavBar];
[self.customNavBar mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.mas_equalTo(self.view);
make.height.mas_equalTo(HEIGHT_STATUSBAR + 44);
}];
}
/// 横竖屏切换时:状态栏高度会发生变化
#ifdef __IPHONE_11_0
- (void)viewSafeAreaInsetsDidChange {
[super viewSafeAreaInsetsDidChange];
[self.customNavBar mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(self.view.bp_safeAreaInsets.top + 44);
}];
}
#endif
2、这个VC的整个页面是个tableView,iPhoneX机器时底部34pt不建议用,特别是某些页面底部是个UIButton时 (适配底部)
///方法一:使用mas_bp_safeAreaLayoutGuideBottom
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:self.tableView];
[_tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.mas_equalTo(self.view);
make.bottom.mas_equalTo(self.view.mas_bp_safeAreaLayoutGuideBottom);
}];
}
///方法二:使用self.view.bp_safeAreaInsets.bottom
- (void)viewSafeAreaInsetsDidChange
{
[super viewSafeAreaInsetsDidChange];
[_tableView mas_updateConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(self.view.mas_bottom).mas_offset(-self.view.bp_safeAreaInsets.bottom);
//如果需求是对齐到底部,只需删除`bp_safeAreaInsets.bottom`
//make.bottom.mas_equalTo(self.view.mas_bottom);
}];
}