首先从iOS7说起,远古时代还没有刘海屏,没有安全区。但作为开发者的我们,日常总会被一些导航条,工具栏等之类的组件所迷惑。有时候你会发现你的页面的顶端与导航条的顶端重合,又或者页面的顶端在导航条的底部,再加上滚动页面这么一个骚操作,就更加扑朔迷离了。现如今还有安全区这个一个概念,往往会发现维护的一些旧项目在新SDK下出了问题,有时候超出安全区,有时候又没有,有时候导航条会滚到安全区外,有时候又不会。我们总会再页面适配的这个问题上有诸多困扰,知道一些操作能解决问题,但有时候换一种情景,这种套路又不再起作用,归根结底,都是文档吃得不透,对技术理解总是流于表面。那么我们今天就从文档开始,从最古老的魔术开始,一点点往下延伸。
isTranslucent
A Boolean value indicating whether the navigation bar is translucent (true) or not (false).
When the navigation bar is translucent, configure the edgesforextendedlayout and extendedlayoutincludesopaquebars properties of your view controller to display your content underneath the navigation bar.
If the navigation bar doesn't have a custom background image, or if any pixel of the background image has an alpha value of less than 1.0, the default value of this property is true. If the background image is completely opaque, the default value of this property is false. If you set this property to true and the custom background image is completely opaque, UIKit applies a system-defined opacity of less than 1.0 to the image. If you set this property to false and the background image is not opaque, UIKit adds an opaque backdrop.
- 系统默认值true.
isTranslucent 是UINavigationBar的一个属性,半透明设置(true=半透明)。如果我们自定义navigationBar的图片,并且图片的alpha通道值小于1.0(即图片本身透明),则isTranslucent的默认值是true,否则是false。当然,如果你设置了一个不透明的背景图,然后你设置isTranslucent为true,那么这个背景图就会被UIKit修改为半透明(设置一个半透明值的alpha?)。如果你的背景图是透明的,当你设置isTranslucent为false时,UIKit会在导航条背景图的底部添加一个不透明的背景来达成不透明的效果。
就是设置背景图会使得isTranslucent的值发生修改,而设置isTranslucent的值会使得导航条达成强制性达成透明或不透明的效果。
另我们偶尔会设置导航条的背景颜色而不是背景图,会使用诸如nv.navigationBar.barTintColor = .white
的语句设置颜色, 发现颜色会与预期的有那么一点差别,这其实也跟isTranslucent有关,如果isTranslucent=true的话,barTintcolor是集中效果的叠合(模糊,透明和原色)。
This color is made translucent by default unless you set the isTranslucent property to false.
edgesForExtendedLayout
Instead of this property, use the safe area of your view to determine which parts of your interface are occluded by other content. For more information, see the safeAreaLayoutGuide and safeAreaInsets properties of UIView.
In iOS 10 and earlier, use this property to report which edges of your view controller extend underneath navigation bars or other system-provided views. The default value of this property is all, and it is recommended that you do not change that value.
If you remove an edge value from this property, the system does not lay out your content underneath other bars on that same edge. In addition, the system provides a default background so that translucent bars have an appropriate appearance. The window’s root view controller does not react to this property.
- 系统默认值 UIRectEdge.all
edgesForExtendedLayout是UIViewController的一个布局属性,它指示着controller view的top, left, right, bottom的四个方向边框的位置,不过这是要配合导航条,底部tabbar这样的特殊组的页面才发挥出它的威力。举个例子,如果edgesForExtendedLayout=.top, 此时controller的view就会顶到屏幕最顶端,不过注意的是如果其他另外三个方向还存在bar话(例如tabbar),view的这三个方向并不会延伸到屏幕,譬如此时有个屏幕底部的tabbar,那么此时view的底部会刚好顶到tabbar的顶部。
如果你想设置controller view四个方向都不延伸到屏幕的话,你可以设置edgesForExtendedLayout=[]
; 如果你想延伸到屏幕的四个方向那就设置为edgesForExtendedLayout=.all
,这也是系统默认的。
请务必牢记,view的延伸是到屏幕边缘,而不是安全区边缘,安全区我会在后面说到。
另外,edgesForExtendedLayout的使用还要配合isTranslucent和extendedLayoutIncludesOpaqueBars来进行。
-
isTranslucent = true
结果: edgesForExtendedLayout 都有效
-
isTranslucent = false
-
extendedLayoutIncludesOpaqueBars = fase
结果: edgesForExtendedLayout不管是什么值,都将不会延伸到屏幕
-
extendedLayoutIncludesOpaqueBars = true
结果: edgesForExtendedLayout 都有效
-
那么现在来说说extendedLayoutIncludesOpaqueBars
A Boolean value indicating whether or not the extended layout includes opaque bars.
- 系统默认值false
这属性主要在bar不透明的情况下才起作用,用于指定在不透明的情况下edgesForExtendedLayout是否还有效。明显的是在bar不透明的情况下,如果不支持extendedLayoutIncludesOpaqueBars,那么edgesForExtendedLayout都变得无效。
补充说明:不透明的意思是isTranslucent为false., 你给navigationBar设置barTintColor的时候,navigationBar看上去也“不透明”,但isTranslucent确实true的。
如果view是scroll view?
Scroll View除了是个普通的view,它还包含了一个content view,滚动的内容就是添加你在这个content view中。如果你想滚动的时候,content view的内容会或不会被navigation bar挡住,你想到怎么处理了么?哦,对了,被挡住还要分完全挡住还是滚动的时候被挡住,就是那种滚动的时候透过navigation bar能隐约地看到滚动内容。
嗯,如果全都不想被挡住,那就设置edgesForExtendedLayout =[]
直接搞定。
想完全挡住,那就设置edgesForExtendedLayout =.all
,不过应该不会有这么奇葩的需求,顶部的内容没法看^...^。
那就来那种又能挡住有不能挡住的....
好咧,客官你的菜稍后就到。
contentInsetAdjustmentBehavior
This property specifies how the safe area insets are used to modify the content area of the scroll view. The default value of this property is UIScrollView.ContentInsetAdjustmentBehavior.automatic.
- 系统默认值UIScrollView.ContentInsetAdjustmentBehavior.automatic
这个属性是在iOS 11.0之后才出现的,主要用来指示scrollView(我们日常中见到的UIScrollView,UITableView,UICollectionView,UIWebView都是scrollview)如何根据安全区来调整content view的inset(注意,这里的inset不是contentInsert,而是adjustedContentInset),以便滚动的内容好友好地展示给用户。
contentInsetAdjustmentBehavior 有几个值:
- never, 不调整content的inset。
这时候 adjustedContentInset = 0 - scrollableAxes, 在滚动方向上调整,并且scrollview必须可滚动才起作用。
这时候 adjustedContentInset = max(0, view.safeAreaInset.top+ contentInset.top - scrollview.frame.origin.y) - automatic, 自动调整,无需根据方向,无需scrollview可滚动,如果scroll view超出安全区或者被bar遮挡。
这时候 adjustedContentInset = max(0, view.safeAreaInset.top + contentInset.top - scrollview.frame.origin.y) - always, 总会根据安全区来调整content view的inset。
这时候 adjustedContentInset = max(0, view.safeAreaInset.top + contentInset.top - scrollview.frame.origin.y) .
这个跟automatic有什么区别?
所以,contentInsetAdjustmentBehavior在非never的情况下,这个式子是成立的:
contentView.frame.origin.y = adjustedContentInset.top+scrollView.frame.origin.y (scrollView.frame.origin.y < view.safeAreaInset.top)。
或
contentView.frame.origin.y = view.safeAreaInset.top + contentInset.top(scrollView.frame.origin.y < view.safeAreaInset.top)。
注意以上情况是基于页面含有navigationBar的条件下进去,并且device mode在portrait模式。
引申说明adjustedContentInset
Use this property to obtain the adjusted area in which to draw content.
The contentInsetAdjustmentBehavior property determines whether the safe area insets are included in the adjustment.
The safe area insets are then added to the values in the contentInset property to obtain the final value of this property.
安全区SafeArea
到这里就该说说安全区了,这里是传送门。
怎么用语言描述安全区呢? 刘海以外的区域? 好像又不太严谨,因为你马上就知道在navigation controller中,childViewController view的safeArea是在导航条下,而navigation controller view的safeArea是在刘海下,基本上view的安全区也是参考superView的安全区和自身的frame做界定的,另外安全区还可以手动调整(但只有controller 的 root view安全区可以调节)。
self.overlayView.snp.makeConstraints { (make) in
make.left.equalToSuperview()
make.top.equalTo(self.view.safeAreaLayoutGuide).offset(-20)
make.width.height.equalTo(50)
}
...
print("super view safe area top: \(self.view.safeAreaInsets.top)")
print("overlayView safe area top:\(self.overlayView.safeAreaInsets.top)")
//Output:
//super view safe area top: 88.0
//overlayView safe area top:20.0
//可以看出此时controller view的safe area top inset为88,
//而overlayView的safe area top inset是20,因为我们给它的布局约束是参考superView的safeAreaLayoutGuide的top而偏移了-20。
//也就是超出的那(0,0,50,20)这块区域在overlayView中是超出了安全区的。
其实,就我们之前了解的edgesForExtendedLayout和contentInsetAdjustmentBehavior也是足够我们应付日常需求的了,但为什么我们还是要关注safeArea? 细心的小伙伴应该会看到前面介绍edgesForExtendedLayout的时候贴出来的官方解释中有这么一句Instead of this property, use the safe area of your view to determine which parts of your interface are occluded by other content. For more information, see the safeAreaLayoutGuide and safeAreaInsets properties of UIView.
。官方在明目张胆地推荐我们用safeAre呀,万一将来edgesForExtendedLayout也像automaticallyAdjustsScrollViewInsets一样被无情地抛弃了呢?
safeAreaLayoutGuide
When the view is visible onscreen, this guide reflects the portion of the view that is not covered by navigation bars, tab bars, toolbars, and other ancestor views. (In tvOS, the safe area reflects the area not covered the screen's bezel.) If the view is not currently installed in a view hierarchy, or is not yet visible onscreen, the layout guide edges are equal to the edges of the view.
For the view controller's root view, the layout guide accommodates the status bar, other visible bars, and any additional insets that you specified using the additionalSafeAreaInsets property of your view controller. For other views in the view hierarchy, the layout guide reflects only the portion of the view that is covered by other content. For example, if a view is entirely within the safe area of its superview, the layout guide edges are equal to the edges of the view.
- 这个layout的范围就是在安全区内(没有被bar遮挡的那部分内容)。
- 只有view在当前视图层级(简单理解为app正在显示的内容)的时候,这个这个属性才有效。
- 如果view已经在安全区里,safeAreaLayoutGuide就等同于view的边框。
- safeAreaLayoutGuide不是本质上的view,不会显示出来。
日常中我们经常会用到Auto Layout,那么safeAreaLayoutGuide就可以作为一个很好的参照物。例如
self.tableView.snp.makeConstraints { (make) in
make.edges.equalTo(self.view.safeAreaLayoutGuide)
}
safeAreaInsets
The insets that you use to determine the safe area for this view.
The safe area of a view reflects the area not covered by navigation bars, tab bars, toolbars, and other ancestors that obscure a view controller's view. (In tvOS, the safe area reflects the area not covered by the screen's bezel.) You obtain the safe area for a view by applying the insets in this property to the view's bounds rectangle. If the view is not currently installed in a view hierarchy, or is not yet visible onscreen, the edge insets in this property are 0.
For the view controller's root view, the insets account for the status bar, other visible bars, and any additional insets that you specified using the additionalSafeAreaInsets property of your view controller. For other views in the view hierarchy, the insets reflect only the portion of the view that is covered. For example, if a view is entirely within the safe area of its superview, the edge insets in this property are 0.
You might use this property at runtime to adjust the position of your view's content programmatically.
这个属性就是用来标识view的当前安全区,只可读。不过要注意2点:
- 如果view不在当前视图层级,或者还没显示在屏幕上,safeAreaInsets就是0.
- 如果view已经在superView的安全区内,那这个view的safeAreaInsets也是0.
- 可以通过controller的additionalSafeAreaInsets属性来在原来的safeAreaInsets基础上做额外的调整。
至此。