目前主要的几种导航栏框架分为三种:

  1. 使用UINavigationController作为viewController的容器,即每次push的时候将viewController作为一个新的UINavigationController的根视图并管理当前viewcontroller。RTRootNavigationController
  2. 对系统的navigationBar进行隐藏或者设置透明,通过一个基类控制器添加一个navigationbar。
  3. 在做转场动画的时候判断两个控制器的navigationbar的样式是否一样,如果不一样就创建一个假的navigationbar。等视图加载完成后删除。

下图是一张A-push-B的方法调用的过程(来源:美团技术)

ios 开发 自定义导航兰封装 ios 自定义导航栏_背景色

虚线内的情况会根据布局是否有变化而调用。

如果我们创建了一个自定义的导航栏组件系统,它的调用顺序可能会与此不同。

下图是B pop A 的流程图。

ios 开发 自定义导航兰封装 ios 自定义导航栏_导航栏_02

 

第三种方式

根据在做转场动画的时候根据navigationbar的style是否一致,来添加一张假的图。腾讯QMUI给出了一种方案,并提供四种方法来决定是否要fake导航栏来模拟。

  1. 判断是否实现了QMUINavigationControllerDelegate协议。该协议中会有外观样式、navigationbar显示隐藏与否。
  2. 判断fromVC和ToVC的customNavigationBarTransitionKey是否。返回的不一致就需要fakebar
  3. 配置表中的automaticCustomNavigationBarTransitionStyle,会设置每次push或者pop是否需要。
  4. fromVC和toVC的导航栏样式是否一致。

这个判断方法

- (BOOL)shouldCustomTransitionAutomaticallyWithFirstViewController:(UIViewController *)viewController1 secondViewController:(UIViewController *)viewController2 {
    
    UIViewController<QMUINavigationControllerDelegate> *vc1 = (UIViewController<QMUINavigationControllerDelegate> *)viewController1;
    UIViewController<QMUINavigationControllerDelegate> *vc2 = (UIViewController<QMUINavigationControllerDelegate> *)viewController2;
    
    //1.
    if (![vc1 conformsToProtocol:@protocol(QMUINavigationControllerDelegate)] || ![vc2 conformsToProtocol:@protocol(QMUINavigationControllerDelegate)]) {
        return NO;// 只处理前后两个界面都是 QMUI 系列的场景
    }
    
    //2.
    if ([vc1 respondsToSelector:@selector(customNavigationBarTransitionKey)] || [vc2 respondsToSelector:@selector(customNavigationBarTransitionKey)]) {
        NSString *key1 = [vc1 respondsToSelector:@selector(customNavigationBarTransitionKey)] ? [vc1 customNavigationBarTransitionKey] : nil;
        NSString *key2 = [vc2 respondsToSelector:@selector(customNavigationBarTransitionKey)] ? [vc2 customNavigationBarTransitionKey] : nil;
        BOOL result = (key1 || key2) && ![key1 isEqualToString:key2];
        return result;
    }
    
    //3
    if (!AutomaticCustomNavigationBarTransitionStyle) {
        return NO;
    }
    
    //4
    UIImage *bg1 = [vc1 respondsToSelector:@selector(navigationBarBackgroundImage)] ? [vc1 navigationBarBackgroundImage] : [[UINavigationBar appearance] backgroundImageForBarMetrics:UIBarMetricsDefault];
    UIImage *bg2 = [vc2 respondsToSelector:@selector(navigationBarBackgroundImage)] ? [vc2 navigationBarBackgroundImage] : [[UINavigationBar appearance] backgroundImageForBarMetrics:UIBarMetricsDefault];
    if (bg1 || bg2) {
        if (!bg1 || !bg2) {
            return YES;// 一个有一个没有,则需要自定义
        }
        if (![bg1.qmui_averageColor isEqual:bg2.qmui_averageColor]) {
            return YES;// 目前只能判断图片颜色是否相等了
        }
    }
    
    // 如果存在 backgroundImage,则 barTintColor、barStyle 就算存在也不会被显示出来,所以这里只判断两个 backgroundImage 都不存在的时候
    if (!bg1 && !bg2) {
        UIColor *barTintColor1 = [vc1 respondsToSelector:@selector(navigationBarBarTintColor)] ? [vc1 navigationBarBarTintColor] : [UINavigationBar appearance].barTintColor;
        UIColor *barTintColor2 = [vc2 respondsToSelector:@selector(navigationBarBarTintColor)] ? [vc2 navigationBarBarTintColor] : [UINavigationBar appearance].barTintColor;
        if (barTintColor1 || barTintColor2) {
            if (!barTintColor1 || !barTintColor2) {
                return YES;
            }
            if (![barTintColor1 isEqual:barTintColor2]) {
                return YES;
            }
        }
        
        UIBarStyle barStyle1 = [vc1 respondsToSelector:@selector(navigationBarStyle)] ? [vc1 navigationBarStyle] : [UINavigationBar appearance].barStyle;
        UIBarStyle barStyle2 = [vc2 respondsToSelector:@selector(navigationBarStyle)] ? [vc2 navigationBarStyle] : [UINavigationBar appearance].barStyle;
        if (barStyle1 != barStyle2) {
            return YES;
        }
    }
    
    UIImage *shadowImage1 = [vc1 respondsToSelector:@selector(navigationBarShadowImage)] ? [vc1 navigationBarShadowImage] : (vc1.navigationController.navigationBar ? vc1.navigationController.navigationBar.shadowImage : (QMUICMIActivated ? NavBarShadowImage : nil));
    UIImage *shadowImage2 = [vc2 respondsToSelector:@selector(navigationBarShadowImage)] ? [vc2 navigationBarShadowImage] : (vc2.navigationController.navigationBar ? vc2.navigationController.navigationBar.shadowImage : (QMUICMIActivated ? NavBarShadowImage : nil));
    if (shadowImage1 || shadowImage2) {
        if (!shadowImage1 || !shadowImage2) {
            return YES;
        }
        if (![shadowImage1.qmui_averageColor isEqual:shadowImage2.qmui_averageColor]) {
            return YES;
        }
    }
    
    return NO;
}

QMUI分别通过UIViewController和UINavigationController的分类通过runtime的方式扩展其方法。

以UIViewController的分类为例(UIViewController +NavigationBarTransition)

分别重载了:viewWillAppear:、viewDidAppear:、viewDidDisappear、viewWillLayoutSubviews这四个声明周期的方法。

以A push B为例,根据图1

  • 第一步B的viewWillAppear,渲染导航栏背景色。这里Controller遵循QMUINavigationControllerAppearanceDelegate协议。因为每个Controller的navigationbar的样式都是通过该协议去设置。
  • 第二步B的layoutSubviews:根据上面的判断方法,判断是否添加fake。
if (isCurrentToViewController && !selfObject.lockTransitionNavigationBar) {
                        
      BOOL shouldCustomNavigationBarTransition = NO;
                        
       //如果没有fakebar
     if (!selfObject.transitionNavigationBar) {
      //判断是否需要fakebar
      if ([selfObject shouldCustomTransitionAutomaticallyWithFirstViewController:fromViewController secondViewController:toViewController]) {
             shouldCustomNavigationBarTransition = YES;
        }
                            
       if (shouldCustomNavigationBarTransition) {
         if (selfObject.navigationController.navigationBar.translucent) {
         // 如果原生bar是半透明的,需要给containerView加个背景色,否则有可能会看到下面的默认黑色背景色
                                    toViewController.originContainerViewBackgroundColor = [transitionCoordinator containerView].backgroundColor;
            [transitionCoordinator containerView].backgroundColor = [selfObject containerViewBackgroundColor];
            }
            //添加fakebar到Controller的view中
            [selfObject addTransitionNavigationBarIfNeeded];
             //设置fakebar的frame
            [selfObject resizeTransitionNavigationBarFrame];
                                selfObject.navigationController.navigationBar.transitionNavigationBar = selfObject.transitionNavigationBar;
             //隐藏系统的bar
              selfObject.prefersNavigationBarBackgroundViewHidden = YES;
             }
        }
   }

- (void)addTransitionNavigationBarIfNeeded {
    
    if (!self.view.qmui_visible || !self.navigationController.navigationBar) {
        return;
    }
    //把原来的样式赋值给fakebar
    UINavigationBar *originBar = self.navigationController.navigationBar;
    _QMUITransitionNavigationBar *customBar = [[_QMUITransitionNavigationBar alloc] init];
    
    if (customBar.barStyle != originBar.barStyle) {
        customBar.barStyle = originBar.barStyle;
    }
    
    if (customBar.translucent != originBar.translucent) {
        customBar.translucent = originBar.translucent;
    }
    
    if (![customBar.barTintColor isEqual:originBar.barTintColor]) {
        customBar.barTintColor = originBar.barTintColor;
    }
    
    UIImage *backgroundImage = [originBar backgroundImageForBarMetrics:UIBarMetricsDefault];
    if (backgroundImage && backgroundImage.size.width <= 0 && backgroundImage.size.height <= 0) {
        // 假设这里的图片时通过`[UIImage new]`这种形式创建的,那么会navBar会奇怪地显示为系统默认navBar的样式。不知道为什么 navController 设置自己的 navBar 为 [UIImage new] 却没事,所以这里做个保护。
        backgroundImage = [UIImage qmui_imageWithColor:UIColorClear];
    }
    [customBar setBackgroundImage:backgroundImage forBarMetrics:UIBarMetricsDefault];
    
    [customBar setShadowImage:originBar.shadowImage];
    
    self.transitionNavigationBar = customBar;
    [self resizeTransitionNavigationBarFrame];
    
    if (!self.navigationController.navigationBarHidden) {
        [self.view addSubview:self.transitionNavigationBar];
    }
    
    CGRect viewRect = [self.navigationController.view convertRect:self.view.frame fromView:self.view.superview];
    if (viewRect.origin.y != 0 && self.view.clipsToBounds) {
        QMUILog(@"UINavigationController+NavigationBarTransition", @"⚠️⚠️⚠️注意啦:当前界面 controller.view = %@ 布局并没有从屏幕顶部开始,可能会导致自定义导航栏转场的假 bar 看不到", self);
    }
}
  • 如果push过程中布局没有改变,那么不会调用虚线的部分
  • A的viewDidDisappear: A控制器调用父类方法。
  • B的viewDidAppear:  重新设置navigationbar并删除fakebar
selfObject.lockTransitionNavigationBar = YES;
                
  f (selfObject.transitionNavigationBar) {
                    
   [UIViewController replaceStyleForNavigationBar:selfObject.transitionNavigationBar withNavigationBar:selfObject.navigationController.navigationBar];
      [selfObject removeTransitionNavigationBar];
                    
    id <UIViewControllerTransitionCoordinator> transitionCoordinator = selfObject.transitionCoordinator;
    [transitionCoordinator containerView].backgroundColor = selfObject.originContainerViewBackgroundColor;
    }
                
   if ([selfObject.navigationController.viewControllers containsObject:selfObject]) {
     // 防止一些 childViewController 走到这里
     selfObject.prefersNavigationBarBackgroundViewHidden = NO;
}

基本的流程大致是这样