拓展:导航栏的灵活运用

起点

问题

由于项目的迭代和持续开发,项目变得越来越大,对于导航栏这一块的要求也越来越高,系统导航栏早已不能满足需求(例如:透明,颜色变化,高度变化等),为了方便以后的开发和更改,一套成熟的自定义导航系统是必需的。

分析导航栏结构

  • 使用XCode中的Debug View Hierarchy模式打开一个具有导航栏的控制器
  • 图层显示如下图:
    图层结构
  • 图层列表如下:
    图层列表
  • 将二者结合起来看,找出对应关系
    对应关系

  • 图中蓝色部分是设置了导航栏背景色,绿色线条指向视图UIView为自定义视图,下面会详细讲到。
    self.navigationController?.navigationBar.setBackgroundImage(UIImage(named:"navibar_bg_blue"), for: .default)

确定方案

  • 由上方图层可知,控制导航栏颜色的是一个UIImageView,需要完成各种自定义需求,需将其设为空,然后添加自定义视图UIView于绿色视图位置,用来改变导航栏颜色。

实现

  • 使用系统导航栏,将导航栏背景变为透明,再在导航栏上添加自定义view来控制导航栏颜色变化。
  • 新建UIViewControllernavigationBarUInavigationcontroller分类,增加UIViewController设置导航栏颜色的属性navBarBackgroundColornavigationBar改变颜色的方法:- (void)cc_setBackgroundColor:(UIColor *)backgroundColor
  • UIViewController分类中使用runtime增加属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    - (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的分类中增加设置背景颜色的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    static 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交换方法来实现。
    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    + (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/