前言
最近关于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
主要由_UIBarBackground
、UINavigationItemView
、_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) { 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。