问题
由于项目的迭代和持续开发,项目变得越来越大,对于导航栏这一块的要求也越来越高,系统导航栏早已不能满足需求(例如:透明,颜色变化,高度变化等),为了方便以后的开发和更改,一套成熟的自定义导航系统是必需的。
分析导航栏结构
- 使用
XCode
中的Debug View Hierarchy
模式打开一个具有导航栏的控制器 - 图层显示如下图:
- 图层列表如下:
将二者结合起来看,找出对应关系
图中蓝色部分是设置了导航栏背景色,绿色线条指向视图
UIView
为自定义视图,下面会详细讲到。self.navigationController?.navigationBar.setBackgroundImage(UIImage(named:"navibar_bg_blue"), for: .default)
确定方案
- 由上方图层可知,控制导航栏颜色的是一个
UIImageView
,需要完成各种自定义需求,需将其设为空,然后添加自定义视图UIView
于绿色视图位置,用来改变导航栏颜色。
实现
- 使用系统导航栏,将导航栏背景变为透明,再在导航栏上添加自定义
view
来控制导航栏颜色变化。 - 新建
UIViewController
、navigationBar
、UInavigationcontroller
分类,增加UIViewController
设置导航栏颜色的属性navBarBackgroundColor
和navigationBar
改变颜色的方法:- (void)cc_setBackgroundColor:(UIColor *)backgroundColor
在
UIViewController
分类中使用runtime
增加属性1234567891011- (void)setNavBarBackgroundColor:(UIColor *)navBarBackgroundColor{[self.navigationController.navigationBar cc_setBackgroundColor:navBarBackgroundColor];objc_setAssociatedObject(self, @selector(navBarBackgroundColor), navBarBackgroundColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (UIColor *)navBarBackgroundColor{UIColor *color = objc_getAssociatedObject(self, @selector(navBarBackgroundColor));return color ? color : navDefaultColor;}在
navigationBar
的分类中增加设置背景颜色的方法1234567891011121314151617181920212223static char overlayKey;- (UIView *)overlay{return objc_getAssociatedObject(self, &overlayKey);}- (void)setOverlay:(UIView *)overlay{objc_setAssociatedObject(self, &overlayKey, overlay, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (void)cc_setBackgroundColor:(UIColor *)backgroundColor{if (!self.overlay) {[self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];self.overlay = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds) + StatusH)];self.overlay.userInteractionEnabled = NO;self.overlay.autoresizingMask = UIViewAutoresizingFlexibleWidth;[[self.subviews firstObject] insertSubview:self.overlay atIndex:0];}self.overlay.backgroundColor = backgroundColor;}完成并运行后发现得到效果和理想中的效果大致相同,但同时出现了新的问题:当使用手势返回的时候会出现导航栏颜色不协调的现象,如果在滑动返回时出现导航栏颜色之间的过度,即VC1导航栏颜色向VC2导航栏颜色过度,在停留在任意VC时都不会出现不协调的效果。
- 接下来需要监听滑动返回手势进度,方便在反动返回时改变或者渐变改变导航栏的颜色。在
UInavigationcontroller
的分类中利用runtime
交换方法来实现。12345678910111213141516171819202122232425262728293031323334353637383940+ (void)initialize{if (self == [UINavigationController self]) {NSArray *arr = @[@"_updateInteractiveTransition:",@"popToViewController:animated:",@"popToRootViewControllerAnimated:"];for (NSString *str in arr) {NSString *new_str = [[@"cc_" stringByAppendingString:str] stringByReplacingOccurrencesOfString:@"__" withString:@"_"];Method A = class_getInstanceMethod(self, NSSelectorFromString(str));Method B = class_getInstanceMethod(self, NSSelectorFromString(new_str));method_exchangeImplementations(A, B);}}}#pragma mark 交换的方法- (void)cc_updateInteractiveTransition:(CGFloat)percentComplete{UIViewController *topVC = self.topViewController;if (topVC) {id <UIViewControllerTransitionCoordinator> transitionContext = topVC.transitionCoordinator;UIViewController *fromVc = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];UIViewController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];UIColor *fromNavBackColor = fromVc.navBarBackgroundColor;UIColor *toNavBackColor = toVc.navBarBackgroundColor;UIColor *newNavBackColor = [self averageColorFromColor:fromNavBackColor toColor:toNavBackColor percent:percentComplete];[self.navigationBar cc_setBackgroundColor:newNavBackColor];[transitionContext notifyWhenInteractionChangesUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {//动画结束是调用if (context.isCancelled) {//动作取消[self.navigationBar cc_setBackgroundColor:fromNavBackColor];}else{//动作完成[self.navigationBar cc_setBackgroundColor:toNavBackColor];}}];}[self cc_updateInteractiveTransition:percentComplete];}
这样就大致的利用分类构建了一套改变导航栏颜色的方案
其它
在这里简单介绍一下另一种导航栏自定义颜色方案,类似于网易云音乐导航栏。
这里附上链接:http://jerrytian.com/