聊聊UINavigationBar

UINavigationBar是蘋果系統(tǒng)自帶的,一個很方便使用的導航欄宅倒,并且在同一個UINavigationController的控制器棧里面抛计,共享一個UINavigationBar逮栅,可以保持統(tǒng)一性,但是钱贯,也因為是共用一個UINavigationBar挫掏,所以,只要控制器棧里面的某一個控制器修改了UINavigationBar會影響所有的控制器秩命,這也算一個弊端尉共。

1.為什么要聊聊UINavigationBar???

在做項目時,比較蛋疼的就是透明導航欄和非透明導航欄的切換問題弃锐,特別是在側滑返回時袄友,上一個頁面可以透過導航欄看到下一個頁面,造成很奇怪的顯示霹菊。所以剧蚣,因為踩坑太多,需要了解一下,導航欄到底是個什么東東鸠按。(PS: 這個問題其實只要當前頁面的view充滿整個屏幕礼搁,就不能透過導航欄看見下一個頁面了。)

2.先來解剖一下UINavigationBar的層級

這里只涉及到iOS8~iOS10目尖,由于沒有iOS8以下的模擬器馒吴,也沒有真機(??),而且現(xiàn)在大部分手機系統(tǒng)都在iOS8以上了瑟曲,iOS11也只是測試版饮戳,不穩(wěn)定,先不管测蹲。下面進入正題:(PS: 下面都是居于iPhone6)


UINavigationBar層級.png

(1)iOS8/iOS9中UINavigationBar的層級結構如下:

UINavigationBar
    —— _UINavigationBarBackground
            —— _UIBackdropView  
            —— _UIBackdropEffectView
            —— UIImageView
    —— UINavigationItemView
    —— UINavigationButton
    —— _UINavigationBarBackIndicatorView

看一下這都是什么類莹捡??扣甲?

UINavigationBar->UIView 
frame = (0 20; 375 44); opaque = NO; autoresize = W

_UINavigationBarBackground->_UIBarBackgroundImageView->UIImageViewframe = (0 -20; 375 64); autoresize = W; userInteractionEnabled = NO

_UIBackdropView->UIView 
frame = (0 0; 375 64); opaque = NO; autoresize = W+H; userInteractionEnabled = NO

_UIBackdropEffectView->UIView 
frame = (0 0; 375 64); clipsToBounds = YES; opaque = NO; autoresize = W+H; userInteractionEnabled = NO

UIImageView->UIView 
frame = (0 64; 375 0.5); userInteractionEnabled = NO 

UINavigationItemView->UIView
frame = (170.5 8; 34 27); opaque = NO; userInteractionEnabled = NO

UINavigationButton->UIButton->UIControl->UIView
frame = (316 7; 51 30); opaque = NO

_UINavigationBarBackIndicatorView->UIImageView
frame = (8 11.5; 13 21); alpha = 0; opaque = NO; userInteractionEnabled = NO

上面可以得出幾點:
1. UINavigationBar的frame是(0 20; 375 44)篮赢,所以UINavigationBar和UIStatusBar并不是混在一起的。
2. _UINavigationBarBackground的frame是(0 -20; 375 64)琉挖,并且是UIImageView類型启泣,也就是我們改變導航欄的背景圖片就是賦值給這個類。
3. UIImageView示辈,不用多說寥茫,這個就是陰影圖片藕赞,它的frame是(0 64; 375 0.5)本今,從64開始算,也就是說茅坛,將UINavigationBar的clipToBounds=Yes就能屏蔽掉底部陰影線险耀。
4. 導航欄的背景 _UINavigationBarBackground弄喘,標題UINavigationItemView,左右按鈕UINavigationButton和返回圖片_UINavigationBarBackIndicatorView都是在同一層級甩牺。
5. _UIBackdropView和_UIBackdropEffectView是給導航欄添加上毛玻璃效果的類蘑志。

(2)iOS10中UINavigationBar的層級結構如下:

UINavigationBar
    —— _UIBarBackground
            —— UIImageView
            —— UIVisualEffectView
            —— _UIVisualEffectBackdropView
            —— _UIVisualEffectFilterView
    —— UINavigationItemView
    —— UINavigationButton
    —— _UINavigationBarBackIndicatorView

iOS10中的導航欄和iOS8/iOS9的導航欄層級結構差不多,區(qū)別在于使用系統(tǒng)提供的毛玻璃類:

UIVisualEffectView->UIView
frame = (0 0; 375 64)

_UIVisualEffectBackdropView->_UIVisualEffectSubview->UIView
frame = (0 0; 375 64); autoresize = W+H; userInteractionEnabled = NO

_UIVisualEffectFilterView->_UIVisualEffectSubview->UIView
frame = (0 0; 375 64); autoresize = W+H; userInteractionEnabled = NO

3.推薦管理導航欄比較好用的第三方庫


WRNavigationBar實現(xiàn)的原理是贬派,把系統(tǒng)的backgroundImage設置為透明急但,然后自己在backgroundView添加背景,設置圖片背景的時候添加imageView搞乏,設置顏色背景時波桩,直接添加view設置背景顏色,透明則通過直接設置backgroundView的alpha值來表現(xiàn)请敦。而底部的陰影分割線只是提供了隱藏和顯示的方法镐躲,也是通過直接隱藏shadowImage來實現(xiàn)的柏卤,核心代碼如下:

@implementation UINavigationBar (WRAddition)

// set navigationBar backgroundImage
- (void)wr_setBackgroundImage:(UIImage *)image
{
    [self.backgroundView removeFromSuperview];
    self.backgroundView = nil;
    if (self.backgroundImageView == nil)
    {
        // add a image(nil color) to _UIBarBackground make it clear
        [self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
        self.backgroundImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), kWRNavBarBottom)];
        // _UIBarBackground is first subView for navigationBar
        [self.subviews.firstObject insertSubview:self.backgroundImageView atIndex:0];
    }
    self.backgroundImageView.image = image;
}

// set navigationBar barTintColor
- (void)wr_setBackgroundColor:(UIColor *)color
{
    [self.backgroundImageView removeFromSuperview];
    self.backgroundImageView = nil;
    if (self.backgroundView == nil)
    {
        // add a image(nil color) to _UIBarBackground make it clear
        [self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
        self.backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), kWRNavBarBottom)];
        // _UIBarBackground is first subView for navigationBar
        [self.subviews.firstObject insertSubview:self.backgroundView atIndex:0];
    }
    self.backgroundView.backgroundColor = color;
}

// set _UIBarBackground alpha (_UIBarBackground subviews alpha <= _UIBarBackground alpha)
- (void)wr_setBackgroundAlpha:(CGFloat)alpha
{
    UIView *barBackgroundView = self.subviews.firstObject;
    barBackgroundView.alpha = alpha;
}


KMNavigationBarTransition實現(xiàn)的原理分解:
KMNavigationBarTransition主要的文件是兩個category,分別是UINavigationController+KMNavigationBarTransitionUIViewController+KMNavigationBarTransition匀油,其中UINavigationController+KMNavigationBarTransitionhook四個方法:

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        KMSwizzleMethod([self class],
                        @selector(pushViewController:animated:),
                        @selector(km_pushViewController:animated:));
        
        KMSwizzleMethod([self class],
                        @selector(popViewControllerAnimated:),
                        @selector(km_popViewControllerAnimated:));
        
        KMSwizzleMethod([self class],
                        @selector(popToViewController:animated:),
                        @selector(km_popToViewController:animated:));
        
        KMSwizzleMethod([self class],
                        @selector(popToRootViewControllerAnimated:),
                        @selector(km_popToRootViewControllerAnimated:));
        
        KMSwizzleMethod([self class],
                        @selector(setViewControllers:animated:),
                        @selector(km_setViewControllers:animated:));
    });
}

UIViewController+KMNavigationBarTransitionhook了兩個方法:

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        KMSwizzleMethod([self class],
                        @selector(viewWillLayoutSubviews),
                        @selector(km_viewWillLayoutSubviews));
        
        KMSwizzleMethod([self class],
                        @selector(viewDidAppear:),
                        @selector(km_viewDidAppear:));
    });
}

以push為例,KMNavigationBarTransition在km_pushViewController:animated:方法里做了以下操作:

- (void)km_pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    UIViewController *disappearingViewController = self.viewControllers.lastObject;
    if (!disappearingViewController) {
        return [self km_pushViewController:viewController animated:animated];
    }
    if (!self.km_transitionContextToViewController || !disappearingViewController.km_transitionNavigationBar) {
    [disappearingViewController km_addTransitionNavigationBarIfNeeded];
    }
    if (animated) {
        self.km_transitionContextToViewController = viewController;
        if (disappearingViewController.km_transitionNavigationBar) {
            disappearingViewController.km_prefersNavigationBarBackgroundViewHidden = YES;
        }
    }
    return [self km_pushViewController:viewController animated:animated];
}

在即將消失的controller勾笆,也就是push的上一個controller添加了一個navigationBar敌蚜,并且將系統(tǒng)的navigationBarBackground隱藏:

- (void)km_addTransitionNavigationBarIfNeeded {
    if (!self.isViewLoaded || !self.view.window) {
        return;
    }
    if (!self.navigationController.navigationBar) {
        return;
    }
    [self km_adjustScrollViewContentOffsetIfNeeded];
    UINavigationBar *bar = [[UINavigationBar alloc] init];
    bar.barStyle = self.navigationController.navigationBar.barStyle;
    if (bar.translucent != self.navigationController.navigationBar.translucent) {
        bar.translucent = self.navigationController.navigationBar.translucent;
    }
    bar.barTintColor = self.navigationController.navigationBar.barTintColor;
    [bar setBackgroundImage:[self.navigationController.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault] forBarMetrics:UIBarMetricsDefault];
    bar.shadowImage = self.navigationController.navigationBar.shadowImage;
    [self.km_transitionNavigationBar removeFromSuperview];
    self.km_transitionNavigationBar = bar;
    [self km_resizeTransitionNavigationBarFrame];
    if (!self.navigationController.navigationBarHidden && !self.navigationController.navigationBar.hidden) {
        [self.view addSubview:self.km_transitionNavigationBar];
    }
}

viewWillLayoutSubviews方法里做了同樣的操作,把即將push的controller添加navigationBar窝爪,然后隱藏系統(tǒng)的navigationBarBackground:

- (void)km_viewWillLayoutSubviews {
    id<UIViewControllerTransitionCoordinator> tc = self.transitionCoordinator;
    UIViewController *fromViewController = [tc viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toViewController = [tc viewControllerForKey:UITransitionContextToViewControllerKey];
    
    if ([self isEqual:self.navigationController.viewControllers.lastObject] && [toViewController isEqual:self] && self.navigationController.km_transitionContextToViewController) {
        if (self.navigationController.navigationBar.translucent) {
            [tc containerView].backgroundColor = [self.navigationController km_containerViewBackgroundColor];
        }
        fromViewController.view.clipsToBounds = NO;
        toViewController.view.clipsToBounds = NO;
        if (!self.km_transitionNavigationBar) {
            [self km_addTransitionNavigationBarIfNeeded];
            
            self.km_prefersNavigationBarBackgroundViewHidden = YES;
        }
        [self km_resizeTransitionNavigationBarFrame];
    }
    if (self.km_transitionNavigationBar) {
        [self.view bringSubviewToFront:self.km_transitionNavigationBar];
    }
    [self km_viewWillLayoutSubviews];
}

viewDidAppear里面將自己添加的navigationBar移除弛车,顯示系統(tǒng)的navigationBar:

- (void)km_viewDidAppear:(BOOL)animated {
    if (self.km_transitionNavigationBar) {
        self.navigationController.navigationBar.barTintColor = self.km_transitionNavigationBar.barTintColor;
        [self.navigationController.navigationBar setBackgroundImage:[self.km_transitionNavigationBar backgroundImageForBarMetrics:UIBarMetricsDefault] forBarMetrics:UIBarMetricsDefault];
        [self.navigationController.navigationBar setShadowImage:self.km_transitionNavigationBar.shadowImage];
        
        UIViewController *transitionViewController = self.navigationController.km_transitionContextToViewController;
        if (!transitionViewController || [transitionViewController isEqual:self]) {
            [self.km_transitionNavigationBar removeFromSuperview];
            self.km_transitionNavigationBar = nil;
            self.navigationController.km_transitionContextToViewController = nil;
        }
    }
    self.km_prefersNavigationBarBackgroundViewHidden = NO;
    [self km_viewDidAppear:animated];
}

整體流程就是,先添加自定義的navigationBar蒲每,隱藏系統(tǒng)的navigationBar纷跛,等push完成就移除自定義的navigationBar,顯示系統(tǒng)的navigationBar邀杏。

總結:

navigationBar是一個讓人又愛又恨的東西贫奠,不過理解了navigationBar的層級關系,到時候望蜡,出了問題或者想要實現(xiàn)一些系統(tǒng)沒有的效果就容易的多了唤崭。并且,一些第三方庫封裝的很好脖律,可以直接用谢肾,我們就不要重復造輪子了。??

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末小泉,一起剝皮案震驚了整個濱河市芦疏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌微姊,老刑警劉巖酸茴,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異柒桑,居然都是意外死亡弊决,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門魁淳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來飘诗,“玉大人,你說我怎么就攤上這事界逛±ジ澹” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵息拜,是天一觀的道長溉潭。 經(jīng)常有香客問我净响,道長,這世上最難降的妖魔是什么喳瓣? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任馋贤,我火速辦了婚禮,結果婚禮上畏陕,老公的妹妹穿的比我還像新娘配乓。我一直安慰自己,他們只是感情好惠毁,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布犹芹。 她就那樣靜靜地躺著,像睡著了一般鞠绰。 火紅的嫁衣襯著肌膚如雪腰埂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天蜈膨,我揣著相機與錄音屿笼,去河邊找鬼。 笑死丈挟,一個胖子當著我的面吹牛刁卜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播曙咽,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛔趴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了例朱?” 一聲冷哼從身側響起孝情,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎洒嗤,沒想到半個月后箫荡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡渔隶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年羔挡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片间唉。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡绞灼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呈野,到底是詐尸還是另有隱情低矮,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布被冒,位于F島的核電站军掂,受9級特大地震影響轮蜕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蝗锥,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一跃洛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧终议,春花似錦税课、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽垒玲。三九已至陆馁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間合愈,已是汗流浹背叮贩。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留佛析,地道東北人益老。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像寸莫,于是被迫代替她去往敵國和親捺萌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容