iOS 11 safeArea详解及iphoneX 适配

2020-01-21 05:01:02于丽

可以看到,viewSafeAreaInsetsDidChange调用时机很早,在viewWillAppear后,这是为什么出现多余动画的原因。并且“pushVC”的safeAreaInsets直到viewSafeAreaInsetsDidChange调用前,都是UIEdgeInsetsZero,之后才是正确的UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)

并且viewSafeAreaInsetsDidChange后面会调用两次viewDidLayoutSubviews,所以我们应该把改变高度或布局的代码都写在viewDidLayoutSubviews里,这样就不会有多余的动画效果了。需要注意viewDidLayoutSubviews可能会由别的操作频繁触发,所以如果调整safeArea布局的代码比较耗时,可以考虑加上一个状态标记,只在didChange后执行一次布局调整

最后的代码应该长这样


- (void)viewDidLayoutSubviews {
 [super viewDidLayoutSubviews];
 UIEdgeInsets safeAreaInsets = sgm_safeAreaInset(self.view);
 CGFloat height = 44.0; // 导航栏原本的高度,通常是44.0
 height += safeAreaInsets.top > 0 ? safeAreaInsets.top : 20.0; // 20.0是statusbar的高度,这里假设statusbar不消失
 if (_navigationbar && _navigationbar.height != height) {
  _navigationbar.height = height;
 }

适配前后的效果

iOS11,safeArea

适配前

iOS11,safeArea

适配后

这样对于frame布局和autolayout布局的各种情况,有了一个动态的适配方案,就是分别使用safeAreaLayoutGuide和safeAreaInsets来灵活处理布局,相比写死一个固定距离,当前和未来的各种机型都能一套代码适配,扩展性更好。我们项目目前也是采用这种做法,如果你的项目需要适配横竖屏或UI控件布局相对复杂,真的应该考虑使用safeArea

顺便提一下,VFL似乎已经废了,因为|只能表示父view的边缘,并没有一个符号来表示父view的safeAreaLayoutGuide的边缘,以前我们写的VFL代码,好多得改,改起来也特别麻烦,建议别再用VFL了

最后一个版本判断的问题,safeAreaInsets和safeAreaLayoutGuide都是iOS11的API,如果不做封装,直接在代码里写,势必会出现大量@available这种版本判断语句,代码里到处是@available,看起来很崩溃,破坏代码可读性。

因为我之前写了一个自动布局框架,这次就将safeAreaLayoutGuide和版本判断都顺便封装在里面了,个人觉得这套框架比NSLayoutAnchor好用,主要作用是简化布局代码书写,以下是生成一个NSLayoutConstraint的对比


// 需求是topLeftView的top等于self.view的safeAreaLayoutGuide的top
// 使用系统API
if (@available(iOS 11.0, *)) {
  [NSLayoutConstraint constraintWithItem:self.topLeftView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view.safeAreaLayoutGuide attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
 } else {
  [NSLayoutConstraint constraintWithItem:self.topLeftView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
 }

// 使用NSLayoutConstraint-SSLayout
self.topLeftView.top_attr = self.view.top_attr_safe