Kealdish's Studio.

UINavigationBar背景色设置

字数统计: 1.3k阅读时长: 5 min
2015/09/24 Share

前言

最近关于UINavigationBar背景色随tableView滚动而渐变的风格很流行,自己也想着研究去实现它,并决定把研究的结果写进这篇文章中。渐变的实现很简单,思路无非是监听tableView的contentOffset属性,在scrollViewDidScroll方法中更新navigationbar背景色的alpha值,我们的重点放在设置navigationbar背景色上。

关于translucent属性

在iOS 7后translucent属性默认为YES,如果你给navigtionbar设置自定义的背景图片,若该图片的alpha值小于1,那么显示时会使用图片本身的alpha值,若该图片不透明,显示时则会应用系统设置的透明度到图片上。如果设置translucent为NO,那么背景图片显示将会正常。

上面是苹果官方文档对于translucent属性的解释,我通过Reveal查看并在控制台打印两种情况下UINavigationbar的视图结构,结果也印证了文档中的说明。

translucent为NO

1
2
3
4
5
6
7
<UINavigationBar: 0x7fe045a02530; frame = (0 20; 375 44); autoresize = W; gestureRecognizers = <NSArray: 0x608000049ae0>; layer = <CALayer: 0x61000003e120>>
| <_UIBarBackground: 0x7fe042405e20; frame = (0 -20; 375 64); userInteractionEnabled = NO; layer = <CALayer: 0x61000003e000>>
| | <UIImageView: 0x7fe0424061c0; frame = (0 64; 375 0.5); userInteractionEnabled = NO; layer = <CALayer: 0x61000003fba0>>
| <<UINavigationItemView: 0x7fe045809b00; frame = (170 8; 35 27); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x61800003d6a0>>: item=<<UINavigationItem: 0x6100001c78f0>: title:'标题'> title=标题>
| | <UILabel: 0x7fe04580a110; frame = (0 3.5; 35 21.5); text = '标题'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x61800009bc60>>
| | | <_UILabelContentLayer: 0x60800003db80> (layer)
| <_UINavigationBarBackIndicatorView: 0x7fe042507680; frame = (8 11.5; 13 21); alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x60800003dfa0>>

从视图结构可以看出,UINavigationbar主要由_UIBarBackgroundUINavigationItemView_UINavigationBarBackIndicatorView三部分组成,很简单。

translucent为YES

1
2
3
4
5
6
7
8
9
10
<UINavigationBar: 0x7fcc06e07280; frame = (0 20; 375 44); opaque = NO; autoresize = W; gestureRecognizers = <NSArray: 0x610000044710>; layer = <CALayer: 0x61800002e560>>
| <_UIBarBackground: 0x7fcc06d01980; frame = (0 -20; 375 64); userInteractionEnabled = NO; layer = <CALayer: 0x6080000303c0>>
| | <UIImageView: 0x7fcc06d03920; frame = (0 64; 375 0.5); userInteractionEnabled = NO; layer = <CALayer: 0x6080000308c0>>
| | <UIVisualEffectView: 0x7fcc06d09e60; frame = (0 0; 375 64); layer = <CALayer: 0x608000030980>>
| | | <_UIVisualEffectBackdropView: 0x7fcc08901a90; frame = (0 0; 375 64); autoresize = W+H; userInteractionEnabled = NO; layer = <UICABackdropLayer: 0x61000002fac0>>
| | | <_UIVisualEffectFilterView: 0x7fcc06d09490; frame = (0 0; 375 64); autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x608000030c40>>
| <<UINavigationItemView: 0x7fcc08e007f0; frame = (170 8; 35 27); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x61800002e740>>: item=<<UINavigationItem: 0x6180001c1fe0>: title:'标题'> title=标题>
| | <UILabel: 0x7fcc08e00d30; frame = (0 3.5; 35 21.5); text = '标题'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x618000095f40>>
| | | <_UILabelContentLayer: 0x61800002fb80> (layer)
| <_UINavigationBarBackIndicatorView: 0x7fcc06c05f30; frame = (8 11.5; 13 21); alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x60000002e780>>

相比于trasulent为NO时,UINavigationbar_UIBarBackground中多了一层UIVisualEffectView。我们都知道UIVisualEffectView类是给背景图层提供虚化效果的,这也解释了文档中对于trasulent为YES时的说明。

设置navigationbar背景色问题

通常要设置navigationbar背景颜色,我们会调用[self.navigationController.navigationBar setBackgroundColor:[UIColor redColor]];方法。运行看看,效果好像跟预想的不太一样。

transulent=YES

transulent=NO

在transulent=YES时,还能看见一点红色,而在transulent=NO时,则完全没效果。这是为嘛呢?回头看看前面UINavigationbar视图结构,你也许就有答案了。

从上图可以看到,UINavigationBar实际上被前面的_UIBarBackground挡住了,故而没有效果。

设置navigationbar背景色解决方案

既然找到了问题的缘由,自然也就有对应的解决方案。我这里只提供两种解决方法,实际上解决方法不止两种,而我选择这两种解决方法是因为一种很便利,一种可扩展性强,基本上这两种解决办法可以满足我们大部分的需求。

先说第一种便利的解决办法。既然_UIBarBackground对象挡住了navigationbar,那就设置_UIBarBackground对象的backgroundColor。由于API中并没有提供关于_UIBarBackground对象,我们只能通过KVC的方式,通过断点调试我们发现UINavigationbar的成员变量如下:

1
2
3
4
5
6
7
8
@implementation UINavigationBar (ZS_BackgroundColor)
- (void)setZS_BackgroundColor:(UIColor *)color{
UIView *originView = [self valueForKey:@"_barBackgroundView"];
originView.backgroundColor = color;
}
@end

从图中我们可以看出有个叫_barBackgroundView_UIBarBackground对象,就它了。通过category去设置_barBackgroundView的背景色,貌似这样就能成功了。编译运行,结果大跌眼镜,并没有任何变化。猜想应该是内部在显示之前重置了_barBackgroundView的背景色,那就意味着此路不通,PASS。

第二种方案是在_barBackgroundView上面插入自定义的view,通过这个view来控制navigationbar的背景色。实现上在category中运行runtime机制将view与指定的key动态绑定,以便可以做更多扩展性的事情,比如QQ空间中UIActivityView加载等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#import "UINavigationBar+BackgroundColor.h"
#import <objc/runtime.h>
@implementation UINavigationBar (ZS_BackgroundColor)
static char overlayKey;
- (UIView *)overlay{
return objc_getAssociatedObject(self, &overlayKey);
}
- (void)setOverlay:(UIView *)overlay{
objc_setAssociatedObject(self, &overlayKey, overlay, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)setZS_BackgroundColor:(UIColor *)backgroundColor{
if (!self.overlay) {
// insert an overlay into the view hierarchy
self.overlay = [[UIView alloc] initWithFrame:CGRectMake(0, -20, [UIScreen mainScreen].bounds.size.width, self.bounds.size.height + 20)];
[self insertSubview:self.overlay aboveSubview:[self valueForKey:@"_barBackgroundView"]];
}
self.overlay.backgroundColor = backgroundColor;
}
@end

设置编译运行,看看效果,没有问题,问题解决!

结语

UINavigationbar的视图层中插入自定义view是很好的解决方案,一方面不破坏视图结构,另一方面提供了更多的扩展性,例如下图QQ空间中的activityView。

CATALOG
  1. 1. 前言
  2. 2. 关于translucent属性
    1. 2.0.1. translucent为NO
    2. 2.0.2. translucent为YES
  • 3. 设置navigationbar背景色问题
    1. 3.0.1. transulent=YES
    2. 3.0.2. transulent=NO
  • 4. 设置navigationbar背景色解决方案
  • 5. 结语