目前主要的幾種導航欄框架分爲三種:
- 使用UINavigationController作爲viewController的容器,即每次push的時候將viewController作爲一個新的UINavigationController的根視圖並管理當前viewcontroller。RTRootNavigationController
- 對系統的navigationBar進行隱藏或者設置透明,通過一個基類控制器添加一個navigationbar。
- 在做轉場動畫的時候判斷兩個控制器的navigationbar的樣式是否一樣,如果不一樣就創建一個假的navigationbar。等視圖加載完成後刪除。
下圖是一張A-push-B的方法調用的過程(來源:美團技術)
虛線內的情況會根據佈局是否有變化而調用。
如果我們創建了一個自定義的導航欄組件系統,它的調用順序可能會與此不同。
下圖是B pop A 的流程圖。
第三種方式
根據在做轉場動畫的時候根據navigationbar的style是否一致,來添加一張假的圖。騰訊QMUI給出了一種方案,並提供四種方法來決定是否要fake導航欄來模擬。
- 判斷是否實現了QMUINavigationControllerDelegate協議。該協議中會有外觀樣式、navigationbar顯示隱藏與否。
- 判斷fromVC和ToVC的customNavigationBarTransitionKey是否。返回的不一致就需要fakebar
- 配置表中的automaticCustomNavigationBarTransitionStyle,會設置每次push或者pop是否需要。
- 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;
}
基本的流程大致是這樣
參考:美團導航欄解決