ios 導航欄(二)——自定義導航欄

目前主要的幾種導航欄框架分爲三種:

  1. 使用UINavigationController作爲viewController的容器,即每次push的時候將viewController作爲一個新的UINavigationController的根視圖並管理當前viewcontroller。RTRootNavigationController
  2. 對系統的navigationBar進行隱藏或者設置透明,通過一個基類控制器添加一個navigationbar。
  3. 在做轉場動畫的時候判斷兩個控制器的navigationbar的樣式是否一樣,如果不一樣就創建一個假的navigationbar。等視圖加載完成後刪除。

下圖是一張A-push-B的方法調用的過程(來源:美團技術)

虛線內的情況會根據佈局是否有變化而調用。

如果我們創建了一個自定義的導航欄組件系統,它的調用順序可能會與此不同。

下圖是B pop A 的流程圖。

 

第三種方式

根據在做轉場動畫的時候根據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;
}
                

基本的流程大致是這樣

參考:美團導航欄解決

        QMUI

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章