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)
(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+KMNavigationBarTransition
和UIViewController+KMNavigationBarTransition
匀油,其中UINavigationController+KMNavigationBarTransition
hook四個方法:
+ (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+KMNavigationBarTransition
hook了兩個方法:
+ (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)沒有的效果就容易的多了唤崭。并且,一些第三方庫封裝的很好脖律,可以直接用谢肾,我們就不要重復造輪子了。??