导航栏的那些事
最近项目里有个需求和导航栏的样式定制有关,深入之后发现之前理解的一些概念有些模糊,刚好趁着这次机会全面整理了一下。
从 iOS7 开始,苹果采用了大量的扁平化和毛玻璃风格,刚升级到 iOS7 之后会发现界面的布局多多少少有一些偏差(当然现在新建的项目没有这方面困扰,不需要经历6到7的适配),适配过程中会发现如下一些属性,
- edgesForExtendedLayout
- translucent
- extendedLayoutIncludesOpaqueBars
- automaticallyAdjustsScrollViewInsets
根据字面意思看上去这些属性很好理解,但是发现他们组合之后会有一些不同的表现,一些奇怪的问题也不知道什么原因导致的。不用担心,接下去我会全面的解析一下这几个属性的含义,保证你再也不怕各种奇怪的导航栏问题啦。
edgesForExtendedLayout + translucent
iOS7 以后,edgesForExtendedLayout 的默认设置是 UIRectEdgeAll,translucent 的默认值是 true。这种组合会使 rootView 的布局从(0,0)开始,即 view 的内容会被导航栏遮挡住,大多数情况下将 edgesForExtendedLayout 修改为 UIRectEdgeNone 就能解决布局被遮挡的问题。将 translucent 设置成 false 也会使 rootView 从导航栏底部开始,但是 translucent = false 时即使将 edgesForExtendedLayout 再改成 UIRectEdgeAll rootView 还是从导航栏底部开始布局。如何可以在导航栏不透明的情况下让 rootView 从(0,0)开始布局呢?苹果也考虑到了这种需求,提供了 extendedLayoutIncludesOpaqueBars 这个属性。
小结:translucent 为 true,rootView 从(0,0)开始布局,修改 edgesForExtendedLayout 属性可以改变布局;translucent 为 false,rootView 从导航栏底部开始布局,修改 edgesForExtendedLayout 属性无法改变布局。
extendedLayoutIncludesOpaqueBars + translucent
前面我们知道了 translucent 为 false 时,修改 edgesForExtendedLayout 也无法使 rootView 从(0,0)开始布局。苹果为此提供了 extendedLayoutIncludesOpaqueBars,字面上理解的意思就是在不透明的导航栏下也全屏显示。
这里多提一点,在 ViewController 的生命周期中有 viewDidLoad,viewWillAppear,viewDidAppear,viewWillDisappear,viewDidDisappear,上述提到的这些属性需要在 viewDidAppear 之前设置好,viewDidAppear 可以认为系统已经根据配置布局好了,在这里展示给用户看。
automaticallyAdjustsScrollViewInsets
automaticallyAdjustsScrollViewInsets 默认值是 true,表示在全屏模式下会自动修改第一个添加到 rootView 的 scrollview 的 contentInset 为(64,0,0,0),这样 scrollview 就不会被导航栏遮挡了。
关于 scrollview 有一个问题比较常见,这里解析一下原因。我们经常会这么使用一个 tableView,
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor greenColor];
self.navigationItem.title = @"Master";
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
_tableView.backgroundColor = [UIColor whiteColor];
_tableView.delegate = self;
_tableView.dataSource = self;
[self.view addSubview:_tableView];
}
这样在默认情况下(translucent = true, edgesForExtendedLayout = UIRectEdgeAll),tableView的显示没有问题。但是当我们将 edgesForExtendedLayout 设置成 UIRectEdgeNone 时,当 tableView 的内容比较多时底部的内容反而显示不下。这就很奇怪了,按照前面的结论,这时候 tableView是从导航栏底部开始布局的,contentInset 也是(0,0,0,0),怎么底部的内容会被遮挡一部分呢?原因在于 self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds]; 初始化时 rootView 的 frame 还是(0,0,screenWidth,screenHeight),只需要在 viewWillLayoutSubviews 中重新修改一下 tableview 的 frame 即可,
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
_tableView.frame = self.view.bounds;
}
UINavigationBar 修改背景色
UINavigationBar 是 UIView 的子类,首先想到的是修改背景色,
self.navigationController.navigationBar.backgroundColor = [UIColor greenColor];
发现这并不是我们想要的效果,为什么绿色变淡了呢?通过 Xcode 的 ViewDebugging 我们可以看到 UINavigationBar 内部还有一些子视图,这些子视图的背景色会遮挡住我们设置的颜色。
查看 UINavigationBar 的接口我们发现 setBackgroundImage,设置
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageWithColor:[UIColor greenColor] size:CGSizeMake(1, 1)] forBarMetrics:UIBarMetricsDefault];
结果如下
小结:设置 UINavigationBar 的 backgroundImage 可以修改导航栏的背景色。
translucent 和 setBackgroundImage
前面提到我们可以通过修改背景图片来修改导航栏的背景色,设置了背景图片后在有些页面我们会遇到一些奇怪的问题,发现原来布局正常的页面显示不对了,会多出一部分空白或者被导航栏遮挡住了。
通过打印出 translucent 的值我们发现设置了纯色的背景图后原来半透明的导航栏变成了不透明的,结合前面提到的 translucent 对布局起点的影响,如果页面是按照半透明情况,即 rootView 从(0,0)开始布局来设置子视图的 frame,那么设置了纯色背景图后 translucent 变成了 false,即 rootView 从(0,64)开始布局。为什么设置背景图片会影响 translucent 呢,通过查看文档发现了如下说明,
/*
New behavior on iOS 7.
Default is YES.
You may force an opaque background by setting the property to NO.
If the navigation bar has a custom background image, the default is inferred
from the alpha values of the image—YES if it has any pixel with alpha
也就是说背景图片如果包含 alpha 的色值,系统会默认将 translucent 设置为 true,没有包含 alpha 色值会将 translucent 设置为 false。这下真相大白了,原来我们前面设置了纯绿色的背景图片,是不包含 alpha 色值的,即系统默认将 translucent 设置成了 false。但这是针对没有手动设置 translucent 值的情况,如果我们手动设置了 translucent,那么系统就不会根据背景图片的 alpha 来修改 translucent。
至此,我们了解了苹果是如何使用这几个属性的,针对 iOS7 以上,这里做一下总结:
- iOS7 以后 translucent 默认为 true,rootView 从(0,0)开始布局,修改 edgesForExtendedLayout 属性可以改变布局;
- translucent 为 false,rootView 从导航栏底部开始布局,修改 edgesForExtendedLayout 属性无法改变布局,可以通过设置 extendedLayoutIncludesOpaqueBars 从(0,0)开始布局;
- automaticallyAdjustsScrollViewInsets 默认值是 true,表示在全屏模式下会自动修改第一个添加到 rootView 的 scrollview 的 contentInset 为(64,0,0,0),用来纠正scrollview在全屏模式下的显示;
- 设置 UINavigationBar 的背景图片可以改变导航栏背景色,如果背景图片包含 alpha 的色值,系统会默认将 translucent 设置为 true,没有包含 alpha 色值会将 translucent 设置为 false。但这是针对没有手动设置 translucent 值的情况,如果我们手动设置了 translucent,那么系统就不会根据背景图片的 alpha 来修改 translucent。