ios中导航栏是开发中常用到的,之前从没有关注过导航栏里面的具体结构。今天抽空具体了解了一下
UINavigationBar的父view是一个叫做UILayoutContainerView的视图,该视图对应的控制器就UINavigationController。
UINavigationController主要三个部分:
- navigationBar
- viewcontrollers
- toolbar
导航栏半透明效果
初学ios时,在学习布局时,UIScrollView及其子类的布局和其它UIview的的布局稍有不同,由于在ios 7中导航栏加入了半透明效果,所以UIView的frame的布局Y点坐标是从导航栏顶部开始算起。而UIScrollView是从导航栏底部开始计算,因为UIViewController中有个属性automaticallyAdjustsScrollViewInsets(默认YES)可以当UIScrollView添加到控制器的view中时,其Y点坐标从导航栏底部开始计算。但是该属性在ios11被废弃,改用UIScrollView的contentInsetAdjustmentBehavior属性进行设置。
而造成这一原因就是导航栏的半透明效果。
一般设置self.navigationController.navigationBar.translucent = NO;就可以取消半透明效果。所有view的Y点都会从导航栏底部计算。
或者在AppDelegate文件方法didFinishLaunchingWithOptions设置
[[UINavigationBar appearance] setTranslucent:NO];
?实用方法
// 透明全局(默认)
- (void)translucentAndAll{
self.navigationController.navigationBar.translucent = YES;
self.edgesForExtendedLayout = UIRectEdgeAll;
// self.automaticallyAdjustsScrollViewInsets = YES;
// self.extendedLayoutIncludesOpaqueBars = NO;
}
//有半透明效果,但是从导航栏底部计算
- (void)translucentAnd64{
self.navigationController.navigationBar.translucent = YES;
self.edgesForExtendedLayout = UIRectEdgeNone;
// self.automaticallyAdjustsScrollViewInsets = YES;
// self.extendedLayoutIncludesOpaqueBars = NO;
}
//没有半透明效果,从导航栏底部计算
- (void)noTranslucentAnd64{
self.navigationController.navigationBar.translucent = NO;
// self.edgesForExtendedLayout = UIRectEdgeNone;
}
//没有半透明效果,从导航栏顶部计算
- (void)noTranslucentAndAll{
self.navigationController.navigationBar.translucent = NO;
self.extendedLayoutIncludesOpaqueBars = YES;
// self.edgesForExtendedLayout = UIRectEdgeNone;
}
导航栏具体结构
给导航栏添加一个背景色和标题,其它还原为默认设置
//导航栏背景色
self.navigationController.navigationBar.barTintColor = [ UIColor orangeColor];
启动项目,使用Debug View Hierarchy 进行检查。
左侧是当前view的一个结构图,UILayoutContainerView下面有两个平级的subview:
- UINavigationTransitionView:在控制器进行push和pop的操作的时候会用到该View,在转场动画里会用到。
- UINavigationBar:导航栏view
UINavigationBar
结构图如下
如图右侧navigationBar的Y点坐标是相对UILayoutContainerView为20(刘海屏是40)。导航栏的高度是44。
navigationBar下还有两个view,_UIBarBackground用于修改背景色等,另一个是ContentView:用于存放BarButton和title。
_UIBarBackground
是所有背景图的父view,默认情况下 _UIBarBackground下有两个View:
- UIImageView,当前的imageView是导航栏下面的一条黑线。如下图
如图右侧,导航栏底部黑线的y点坐标是64,高度0.33,说明导航栏下面的黑线是超出navigationBar的范围的。
去除导航栏底部黑线的常用下面方法,给这个ImageView一个空的图片对象。
[self.navigationController.navigationBar setShadowImage:[UIImage new]];
也可以用
self.navigationController.navigationBar.clipsToBounds = YES;
使得超出navigationBar范围的视图不展示。
- UIVIsualEffectView(ios8新增)
这个view的作用就是实现导航栏的半透明效果,如果要实现毛玻璃效果可以使用该view。背景色设置在该view的子视图中。
如果把translucent设置为NO,导航栏的结构是否会发生什么变化?
我们发现UIVisualEffectView这个view没有了,可以推测实现半透明效果就是通过该view实现。并且背景色设置在了_UIBarBackground中。
_UINavigationBarContentVIew
_UIBarBackground在导航栏控制器是不会变的,当你在一个控制器修改了其背景色,退出该控制器是要还原其原来的背景色。
而_UINavigationBarContentView里面的值是不断变换的。通过下面的这个对象给其赋值
UIViewController有一个属性
@property(nonatomic,readonly,strong) UINavigationItem *navigationItem;
可以给当前控制器导航栏修改,中间及左右的按钮。UINavigationItem是一个继承于NSObeject的类,类似于一个model用于存储每个导航栏控制器中的数据,初始化时会有一些默认值。当我们根据需要可以给这个model进行添加或者修改一些参数。
每当跳转一个新的控制器,_UINavigationBarContentView里面的值就会发生改变。那么如何获取旧的控制器中导航栏的信息呢?
navigationBar中有一个属性items,是一个UINavigationItem类型的栈。栈底是第一个控制器的信息。
@property(nullable,nonatomic,copy) NSArray<UINavigationItem *> *items;
在开发中,如果没有特殊需要自定义的导航栏,添加背景图片时可以使用下面方法
- (void)setBackgroundImage:(nullable UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics
该方法是给导航栏添加一个背景图,在导航栏的view结构中会在_UIBarBackground中会在在黑色底线前面插入一个imageView。