一. UINavigationController的結(jié)構(gòu)
UINavigationController
的結(jié)構(gòu)大概分為如下三個(gè)區(qū)域:
對應(yīng)的更加立體的結(jié)構(gòu)如下圖:
其中UILayoutContainerView
對應(yīng)的是self.navigationController.view
,我們可以打個(gè)斷點(diǎn)來驗(yàn)證一下:
UINavigationTransitionView
轉(zhuǎn)場的視圖,也就是我們說的內(nèi)容區(qū)
UINavigationBar
即導(dǎo)航欄
其中ToolBar
默認(rèn)是隱藏的 我們可以手動(dòng)顯示 不過我們一般很少用到它:
self.navigationController.toolbarHidden = NO;
二. UINavigationTransitionView 內(nèi)容區(qū)
在iOS 7.0之前我們的導(dǎo)航欄是擬物化風(fēng)格的,導(dǎo)航條是不透明的,內(nèi)容區(qū)是在導(dǎo)航欄下緊挨著的(Y值從64開始)
但是從iOS 7.0以后 我們的導(dǎo)航欄變成了扁平化風(fēng)格即纲,導(dǎo)航欄是透明的了,也就是說ViewController默認(rèn)使用全屏布局
為了更好的過渡,蘋果從iOS 7.0以后新增了幾個(gè)屬性 我們一一為大家講解
1. edgesForExtendedLayout
edgesForExtendedLayout
是一個(gè)類型為UIRectEdge
的屬性县忌,可以指定邊緣要延伸的方向怀读。因?yàn)閕OS7之后鼓勵(lì)全屏布局绸栅,它的默認(rèn)值是UIRectEdgeAll
劲够,四周邊緣均延伸职车,就是說如果即使視圖中上有navigationBar
,下有tabBar
峦失,那么視圖仍會(huì)延伸覆蓋到四周的區(qū)域。
效果一:導(dǎo)航欄透明 并且內(nèi)容全屏布局
先來看一段非常普通的代碼
- (void)viewDidLoad {
[super viewDidLoad];
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
}
運(yùn)行效果如下圖:
可以看到紅色的View的bounds是從屏幕左上角開始的,而不是導(dǎo)航欄左下方.
其實(shí)等價(jià)于
//導(dǎo)航欄透明 并且使用全屏布局
self.navigationController.navigationBar.translucent = YES;
self.edgesForExtendedLayout = UIRectEdgeAll;
....
效果二:導(dǎo)航欄透明 非全屏布局
那么如果此時(shí)我們想讓redView
的bounds從導(dǎo)航欄的左下方開始該如何操作呢?其實(shí)我們只需要改動(dòng)一句代碼即可:
//不讓View延展到整個(gè)屏幕
self.edgesForExtendedLayout = UIRectEdgeNone;
效果圖如下:
效果三:導(dǎo)航欄不透明 非全屏布局
如果我們將導(dǎo)航欄設(shè)置為不透明效果會(huì)如何呢?
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.navigationBar.translucent = NO;
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
}
可以看到我們將
navigationBar.translucent
設(shè)置為NO
之后 控制器自動(dòng)變?yōu)榉侨敛季至耸趼穑簿褪堑葍r(jià)于
self.edgesForExtendedLayout = UIRectEdgeNone;
效果四:導(dǎo)航欄不透明 全屏布局
如果導(dǎo)航欄不透明但是又要實(shí)現(xiàn)全屏布局的效果 該如何操作呢?
此時(shí)只需要將extendedLayoutIncludesOpaqueBars
設(shè)置為YES
即可:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.navigationBar.translucent = NO;
self.extendedLayoutIncludesOpaqueBars = YES;
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
}
2. extendedLayoutIncludesOpaqueBars
首先我們來看看官方對該屬性的定義:
// Defaults to NO, but bars are translucent by default on 7_0.
@property(nonatomic,assign) BOOL extendedLayoutIncludesOpaqueBars NS_AVAILABLE_IOS(7_0);
擴(kuò)展布局是否包括不透明的Bars尉辑,默認(rèn)為NO
蘋果這樣做其實(shí)是很人性化的,如果bars不透明的情況下较屿,再使擴(kuò)展布局到bars的下方隧魄,這樣感覺是毫無意義的,所以在bars不透明的情況下隘蝎,默認(rèn)不會(huì)延伸布局购啄。
3. automaticallyAdjustsScrollViewInsets
從導(dǎo)航視圖Push
進(jìn)來的以ScrollView
為主的視圖,本來我們的cell是放在(0,0)
的位置上的嘱么,但是考慮到導(dǎo)航欄狮含、狀態(tài)欄會(huì)擋住后面的主視圖,所以系統(tǒng)會(huì)自動(dòng)把我們的內(nèi)容向下偏移64px
(下方位置如果是tarbar則向上偏移49px
)
我們以tableView
為例子來驗(yàn)證一下,如下圖:
可以看到默認(rèn)情況下Cell
的顯示是從導(dǎo)航欄下方開始的,我們可以打印一下tableView
的信息查看一下,如下圖:
可以看到默認(rèn)情況下系統(tǒng)將contentOffset
下移了64
那么曼振,當(dāng)我們不想讓系統(tǒng)自動(dòng)為我們下移時(shí)我們可以這樣設(shè)置:
if (@available(iOS 11.0, *)) {
self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
self.automaticallyAdjustsScrollViewInsets = NO;
}
3. 導(dǎo)航控制器通過棧來管理子控制器
UINavigationController
是通過棧的形式來管理控制器的,如何來驗(yàn)證這一點(diǎn)呢? 我們新建四個(gè)控制器,然后從第一個(gè)控制器依次 push
到最后一個(gè)控制器 此時(shí)我們打印 po self.navigationController.viewControllers
如圖:
當(dāng)我們打印當(dāng)前可視控制器的時(shí)候,顯示的就是棧頂?shù)目刂破?如圖:
當(dāng)然我們從pushViewController:animated:
和 popViewController:animated:
這兩個(gè)方法的描述也可以看出來導(dǎo)航欄的運(yùn)作原理.
三. UINavigationBar 導(dǎo)航欄區(qū)域
1. UINavigationBar
UINavigationBar
繼承自 UIView
, 它主要用來管理導(dǎo)航欄的items
的,我們可以看到它里面有一個(gè)items
的數(shù)組:
@property(nullable,nonatomic,copy) NSArray<UINavigationItem *> *items;
和UINavigationController
一樣,它也是通過棧來管理items
的,而且和self.navigationController.viewControllers
是一一對應(yīng)的:
如下圖:
2. UINavigationItem
UINavigationItem
繼承自NSObject
而不是UIView
几迄,所以他是一個(gè)模型而不是一個(gè)視圖,我們可以使用self.navigationItem
來獲取當(dāng)前頁面導(dǎo)航欄上顯示的全部信息冰评,包括title映胁、titleView 、leftBarButtonItem甲雅、rightBarButtonItem解孙、backBarButonItem
等
因?yàn)樗且粋€(gè)模型所以當(dāng)導(dǎo)航欄 push
一個(gè)控制器的時(shí)候,他是時(shí)時(shí)刷新變化的务荆,展示的是當(dāng)前控制器的Item信息。 如果我們想讓導(dǎo)航欄有一個(gè)固定不變的控件的話 我們可以向 UINavigationBar
中添加一個(gè)子控件即可:
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 150, 30)];
redView.backgroundColor = [UIColor redColor];
[self.navigationController.navigationBar addSubview:redView];
這樣每個(gè)頁面都包含了這個(gè)控件
3. 導(dǎo)航欄透明效果
比較暴力的方式:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
//處理導(dǎo)航欄有條線的問題
[self.navigationController.navigationBar setShadowImage:[UIImage new]];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.navigationController.navigationBar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
[self.navigationController.navigationBar setShadowImage:nil];
}
當(dāng)然我們也可以逐個(gè)去遍歷navigationBar
的子視圖,然后改變他們的透明度
// 設(shè)置導(dǎo)航欄背景透明度
- (void)setNavigationBackgroundAlpha:(CGFloat)alpha {
if (!self.navigationController.navigationBar.isTranslucent) return;
// 導(dǎo)航欄背景透明度設(shè)置
UIView *barBackgroundView;// _UIBarBackground
UIImageView *backgroundImageView;// UIImageView
UIView *backgroundEffectView;// UIVisualEffectView
if (@available(iOS 10.0, *)) {//
barBackgroundView = [self.navigationController.navigationBar.subviews objectAtIndex:0];//_UIBarBackground
backgroundImageView = [barBackgroundView.subviews objectAtIndex:0];//UIImageView
if (backgroundImageView != nil && backgroundImageView.image != nil) {
barBackgroundView.alpha = alpha;
} else {
backgroundEffectView = [barBackgroundView.subviews objectAtIndex:1];//backgroundEffectView
if (backgroundEffectView != nil) {
backgroundEffectView.alpha = alpha;
}
}
}else{
for (UIView *view in self.navigationController.navigationBar.subviews) {
if ([view isKindOfClass:NSClassFromString(@"_UINavigationBarBackground")]) {
barBackgroundView = view;
barBackgroundView.alpha = alpha;
break;
}
}
for (UIView *otherView in barBackgroundView.subviews) {
if ([otherView isKindOfClass:NSClassFromString(@"UIImageView")]) {
backgroundImageView = (UIImageView *)otherView;
backgroundImageView.alpha = alpha;
}else if ([otherView isKindOfClass:NSClassFromString(@"_UIBackdropView")]) {
backgroundEffectView = otherView;
backgroundEffectView.alpha = alpha;
}
}
}
// 對導(dǎo)航欄下面那條線做處理
self.navigationController.navigationBar.clipsToBounds = alpha == 0.0;
}
在使用的時(shí)候我們可以:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setNavigationBackgroundAlpha:0.0];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self setNavigationBackgroundAlpha:1.0];
}
如果想在透明導(dǎo)航欄間平滑的切換 可以參考GitHub和iOS透明導(dǎo)航欄的平滑過渡(進(jìn)階版)
4. 導(dǎo)航欄隱藏
在項(xiàng)目中經(jīng)常碰到首頁頂部是無限輪播,需要靠最上面顯示.然后push到下一個(gè)頁面的時(shí)候是需要導(dǎo)航欄的
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
[self.navigationController setNavigationBarHidden:YES animated:animated];
}
-(void)viewWillDisappear:(BOOL)animated
{
self.navigationController.navigationBarHidden = NO;
[super viewWillDisappear:animated];
}
5. UIBarButtonItem設(shè)置間距
如上圖 ,當(dāng)導(dǎo)航條上有多個(gè)Item的時(shí)候穷遂,如果我們想調(diào)節(jié)兩個(gè)Item之間的距離函匕,讓他們的距離更長該如何操作呢?其實(shí)我們只需要添加一個(gè)
UIBarButtonSystemItemFixedSpace
樣式的Item即可:
UIBarButtonItem *helpBtn = [[UIBarButtonItem alloc] initWithTitle:@"幫助" style:UIBarButtonItemStylePlain target:self action:nil];
UIBarButtonItem *flexBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:self action:nil];
flexBtn.width = 45;
UIBarButtonItem *moreBtn = [[UIBarButtonItem alloc] initWithTitle:@"更多" style:UIBarButtonItemStylePlain target:self action:nil];
self.navigationItem.rightBarButtonItems = @[moreBtn,flexBtn,helpBtn];
效果圖如下:
6. 變更返回圖片
如果我們想變更導(dǎo)航條上的返回圖標(biāo):
UIImage *backImg = [[UIImage imageNamed:@"arrow"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
self.navigationController.navigationBar.backIndicatorImage = backImg;
self.navigationController.navigationBar.backIndicatorTransitionMaskImage = backImg;
效果如下:
此時(shí)如果我們想讓返回圖標(biāo)后面的文字消失該怎么做呢蚪黑?
[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -60) forBarMetrics:UIBarMetricsDefault];
該方法在iOS11的時(shí)候會(huì)出現(xiàn)返回圖標(biāo)下沉效果盅惜,如下圖:
此時(shí)我們可以:
[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(-100, 0) forBarMetrics:UIBarMetricsDefault];
這樣就正常了,如圖:
7. 設(shè)置文字顏色和大小
[navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor redColor],NSFontAttributeName:[UIFont systemFontOfSize:25]}];
8. 設(shè)置背景圖片
[navigationBar setBackgroundImage:[UIImage imageNamed:] forBarMetrics:UIBarMetricsDefault];
9. 全屏手勢滑動(dòng)
- (void)viewDidLoad {
[super viewDidLoad];
//設(shè)置手勢代理
self.interactivePopGestureRecognizer.enabled = NO;
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self.interactivePopGestureRecognizer.delegate action:NSSelectorFromString(@"handleNavigationTransition:")];
[self.view addGestureRecognizer:pan];
pan.delegate = self;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
return self.viewControllers.count>1;
}
10.返回到指定控制器
方法一:
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:2] animated:YES];
方法二:
for (UIViewController *temp in self.navigationController.viewControllers) {
if ([temp isKindOfClass:[要跳轉(zhuǎn)回的控制器 class]]) {
[self.navigationController popToViewController:temp animated:YES];
}
}
10.點(diǎn)擊TabBarItem直接push操作 比如跳轉(zhuǎn)到登錄界面
第一步:在自定義TabBarController
中遵守 UITabBarControllerDelegate
協(xié)議
第二步: self.delegate = self;
第三步:實(shí)現(xiàn) - (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
方法
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
NSLog(@"--tabbaritem.title--%@", viewController.tabBarItem.title);
//這里我判斷的是當(dāng)前點(diǎn)擊的tabBarItem的標(biāo)題
if ([viewController.tabBarItem.title isEqualToString:@"我的"]) {
//如果用戶ID存在的話忌穿,說明已登陸
if (USER_ID) {
return YES;
}
else
{
//跳到登錄頁面
HPLoginViewController *login = [[HPLoginViewController alloc] init];
//隱藏tabbar
login.hidesBottomBarWhenPushed = YES;
[((UINavigationController *)tabBarController.selectedViewController) pushViewController:login animated:YES];
return NO;
}
} else{
return YES;
}
}
本文會(huì)持續(xù)更新抒寂,包括在工作中遇到的關(guān)于導(dǎo)航條的問題我都會(huì)記錄再此......