版本
Xcode 10.2
iPhone 6s (iOS12.4)
目錄
版本
繼承關(guān)系
簡介
創(chuàng)建
切換視圖控制器
UINavigationBar & UIToolBar
其他方法屬性
返回按鍵
繼承關(guān)系
UINavigationController : UIViewController : UIResponder UIResponder : NSObject
簡介
導(dǎo)航控制器是容器視圖控制器全闷,其管理導(dǎo)航界面中的一個(gè)或多個(gè)子視圖控制器打洼。在這種類型的界面中闯传,一次只能看到一個(gè)子視圖控制器沧踏。視圖控制器間的切換: 使用動(dòng)畫在屏幕上推出新的視圖控制器,從而隱藏先前的視圖控制器膀藐。點(diǎn)擊頂部導(dǎo)航欄中的后退按鈕將刪除頂視圖控制器亮靴,從而顯示下方的視圖控制器炕贵。
以Apple自家的設(shè)置App為例, 點(diǎn)擊Setting中的General會推出General界面, 點(diǎn)擊Auto-Lock會推出Auto-Lock界面. 如下圖:
UINavigationController使用有序數(shù)組(稱為導(dǎo)航堆棧)管理其子視圖控制器。數(shù)組中的第一個(gè)視圖控制器是根視圖控制器 (沒有rootViewController屬性, 使用viewControllers[0]獲取)劣光,表示堆棧的底部袜蚕。數(shù)組中的最后一個(gè)視圖控制器是堆棧中最頂層視圖控制器(topViewController)。我們可以使用segue或使用此類的方法從堆棧中添加和刪除視圖控制器, 還可以使用導(dǎo)航欄中的后退按鈕或使用左邊滑動(dòng)手勢來移除最頂層的視圖控制器绢涡。
結(jié)構(gòu)
結(jié)構(gòu)圖中主要有三個(gè)部分: 頂部的UINavigationBar, 底部默認(rèn)隱藏的UIToolBar, 以及中間content部分存放子視圖控制器的view.
UINavigationController是一個(gè)容器視圖控制器 , 也就是說牲剃,它將其他視圖控制器的內(nèi)容嵌入其自身內(nèi)部。我們可以使用其view屬性訪問導(dǎo)航控制器的視圖雄可。
雖然導(dǎo)航欄和工具欄視圖的內(nèi)容發(fā)生更改凿傅,但視圖本身是不變的缠犀。實(shí)際更改的唯一視圖是導(dǎo)航堆棧上最頂層視圖控制器提供的自定義內(nèi)容視圖。
管理的對象
如圖, 導(dǎo)航控制器主要管理四個(gè)對象: 子視圖控制器, 導(dǎo)航欄, 工具欄, 其delegate對象.
- 導(dǎo)航控制器管理子視圖控制器的入棧出棧以及顯示等, 中間部分顯示的view就是頂層視圖控制器topViewController的view聪舒。
- 導(dǎo)航欄始終存在并由導(dǎo)航控制器本身管理辨液,導(dǎo)航控制器使用其子視圖控制器提供的內(nèi)容更新導(dǎo)航欄. 比如, 導(dǎo)航欄上的返回按鈕后面緊跟上一個(gè)界面的title。
- 類似的, 當(dāng)toolbarHidden屬性為NO時(shí)箱残,導(dǎo)航控制器使用其子視圖控制器提供的內(nèi)容更新工具欄滔迈。
- UINavigationController依賴其delegate對象來協(xié)調(diào)自身的行為. 例如, delegate對象(視圖控制器)可以實(shí)現(xiàn)UINavigationControllerDelegate的代理方法, 從而自定義動(dòng)畫過渡, 或者重新制定導(dǎo)航控制器的指向.
創(chuàng)建
新建模板App. 新建類(比如NavigationController)繼承自UINavigationController, storyboard中拖入一個(gè)UINavigationController, class改為我們自己創(chuàng)建的類(NavigationController). 拖入的UINavigationController默認(rèn)的rootViewController為UITableViewController, 刪除這個(gè)UITableViewController, 然后從UINavigationController引一條線(按Ctrl或者鼠標(biāo)右鍵)到原來的ViewController, 選擇root view controller, 此時(shí)ViewController即變成跟視圖控制器. 最后一步, 將ViewController左邊的小箭頭”->”拖動(dòng)到UINavigationController, 這個(gè)小箭頭代表初始啟動(dòng)App調(diào)用的控制器, 我們也可以改變右邊面板中的Is Initial View Controller選項(xiàng)來改變初始控制器.
完成以上操作, 工程即包含了NavigationController導(dǎo)航控制器和ViewController初始視圖控制器.
使用代碼創(chuàng)建思路類似, 此處不演示.
切換視圖控制器
1. 代碼切換
當(dāng)不使用導(dǎo)航控制器的時(shí)候, 我們使用UIViewController的以下方法來跳轉(zhuǎn)視圖
// 載入視圖控制器 (入棧)
- presentViewController:animated:completion:
// 移除視圖控制器 (出棧)
- dismissViewControllerAnimated:completion:
而使用導(dǎo)航控制器時(shí), 我們可以使用
// 載入視圖控制器 (入棧)
- showViewController:sender:
// 載入視圖控制器 (入棧)
- pushViewController:animated:
// 移除視圖控制器 (出棧)
- popViewControllerAnimated:
// 移除到指定視圖控制器 (出棧)
- popToViewController:animated:
// 移除到根視圖控制器 (出棧)
- popToRootViewControllerAnimated:
關(guān)于"- showViewController:sender:"和"- pushViewController:animated:"的區(qū)別
- 當(dāng)當(dāng)前視圖控制器是導(dǎo)航控制器的子控制器時(shí) (即self.navigationController不等于nil), 調(diào)用”- showViewController:sender:”方法, 系統(tǒng)通過一番處理后最終會調(diào)用”- pushViewController:animated:”方法并默認(rèn)使用動(dòng)畫效果 (animated=YES).
- 當(dāng)不使用導(dǎo)航控制器時(shí) (即self.navigationController等于nil), 調(diào)用”- showViewController:sender:”方法, 系統(tǒng)通過一番處理后最終會調(diào)用”- presentViewController:animated:completion:”方法并默認(rèn)使用動(dòng)畫效果 (animated=YES).
- 當(dāng)使用UISplitViewController分離視圖控制器時(shí), 最好調(diào)用”- showViewController:sender:”方法, 具體此處不討論.
2. 使用segue
有兩個(gè)view controller A 和 B, 在A中的某一子控件(一般是button)按住Ctrl或者鼠標(biāo)右鍵拉一條線至B, 會出現(xiàn)以下選項(xiàng).
選擇show, 然后segue就形成了. 使用的時(shí)候點(diǎn)擊A中的這個(gè)觸發(fā)子控件就可以跳轉(zhuǎn)至B了.
關(guān)于segue的幾種類型 (已經(jīng)遺棄的類型不予討論)
-
Show
對應(yīng)代碼方法-showViewController:sender:
將目標(biāo)視圖控制器推到導(dǎo)航堆棧上,從右向左滑動(dòng)疚宇,提供返回按鈕. 如果未嵌入導(dǎo)航控制器亡鼠,它將以模態(tài)方式顯示.
示例:點(diǎn)擊某一內(nèi)容顯示另一個(gè)視圖界面鋪滿屏幕
-
Show Detail
對應(yīng)代碼方法-showDetailViewController:sender:
用于拆分視圖控制器(UISplitViewController)時(shí),在展開的2列界面中替換詳細(xì)/輔助視圖控制器敷待,否則如果折疊為1列间涵,則將推入導(dǎo)航控制器.
示例:在"設(shè)置"中點(diǎn)擊"通用"選項(xiàng), iPhone推出完整界面鋪滿屏幕, iPad推出第2列界面.
-
Present Modally
對應(yīng)代碼方法-presentViewController:animated:completion:
呈現(xiàn)出的各種帶動(dòng)畫效果的視圖控制器,覆蓋前一個(gè)視圖控制器. 在iPhone中, 新的VC從底部動(dòng)畫向上彈出并覆蓋整個(gè)屏幕; 在iPad上通常將新的VC顯示為居中的框榜揖,使原來的VC變暗.
示例:在“設(shè)置”中選擇“觸摸ID和密碼”
-
Present As Popover
對應(yīng)代碼方法 iPad(-presentPopoverFromRect:inView:permittedArrowDirections:animated:), iPhone(-presentViewController:animated:completion:)
在iPhone中勾哩,默認(rèn)情況下新的VC會在整個(gè)屏幕上以模態(tài)方式顯示; 在iPad上運(yùn)行時(shí),新的VC以彈窗形式顯示在點(diǎn)擊處的旁邊举哟,點(diǎn)擊此彈出框外的任何位置都會回收推出這個(gè)新VC.
示例:點(diǎn)擊日歷中的+按鈕
-
Custom
我們可以實(shí)現(xiàn)自己的自定義segue并控制其行為, 但是不推薦使用已經(jīng)廢棄的segue類型, 因?yàn)檫@些segue類型在iOS 8中已棄用:Push思劳,Modal,Popover妨猩,Replace潜叛。
3. 使用代碼+segue
在storyboard中, 我們經(jīng)常用segue線把相關(guān)的VC連接起來, 使之看起來更整齊有序. But, 有時(shí)候我們不希望綁定segue的起點(diǎn)為某個(gè)指定的觸發(fā)按鍵, 而是希望在恰當(dāng)?shù)臅r(shí)機(jī)使用代碼來調(diào)用segue. 那么我們可以這樣做: 從VC1界面的View Controller處引線至VC2界面, 點(diǎn)擊生成segue線, 在右邊面板中找到Identifier屬性并自定義一個(gè)值segueVC2ID, 然后在VC1中跳轉(zhuǎn)代碼如下:
[self performSegueWithIdentifier:@"segueVC2ID" sender:nil];
如果我們綁定了segue的起點(diǎn)為某個(gè)觸發(fā)按鍵, 但是有些情況下我們希望做攔截判斷是否真的需要跳轉(zhuǎn), 可以在VC1中實(shí)現(xiàn)如下方法:
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([identifier isEqualToString:@"segueVC2ID"]) {
return NO;
}
return YES;
}
如果我們想要知道segue的ID, 源VC, 目標(biāo)VC, 可以實(shí)現(xiàn)以下方法:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
NSLog(@"identifier:%@", segue.identifier);
NSLog(@"sourceViewController:%@", segue.sourceViewController);
NSLog(@"destinationViewController:%@", segue.destinationViewController);
}
UINavigationBar & UIToolBar
在了解UINavigationBar和UIToolBar之前, 我們先來看看UINavigationController、UIViewController壶硅、UINavigationBar威兜、UIToolBar、UINavigationItem庐椒、toolbarItems之間的關(guān)系:
- UINavigationController用于管理多個(gè)UIViewController, 將UIViewController的view添加到content中顯示. 也就是說, 多個(gè)UIViewController共用同一個(gè)UINavigationController.
- UINavigationBar椒舵、UIToolBar均是UINavigationController的屬性. 也就是說, 多個(gè)UIViewController共用同一個(gè)UINavigationController、UINavigationBar约谈、UIToolBar.
- UINavigationItem是UIViewController的屬性, 用于管理標(biāo)題title, 左邊按鈕(leftBarButtonItems), 右邊按鈕(rightBarButtonItems)等. 每個(gè)UIViewController的UINavigationItem均不同, 但其管理的控件都顯示在UINavigationBar上面.
- 類似的, toolbarItems是UIViewController的屬性, 用于管理底部的按鈕. 每個(gè)UIViewController的toolbarItems均不同, 但其管理的控件都顯示在UIToolBar上面.
- UINavigationBar笔宿、UIToolBar主要用于設(shè)置"全局變量", 比如位置布局, 主體顏色, 字體大小等等; UINavigationItem、toolbarItems則是每個(gè)UIViewController不同的屬性, 用于設(shè)置VC各自的標(biāo)題, 按鈕功能等.
對照圖片看效果更佳:
示例代碼:
/* --- navigationController屬性 --- */
// 點(diǎn)擊隱藏navigationBar和toolbar, 再次點(diǎn)擊顯示
self.navigationController.hidesBarsOnTap = YES;
// 向上輕掃隱藏, 向下輕掃顯示
self.navigationController.hidesBarsOnSwipe = YES;
// 鍵盤出現(xiàn)隱藏, 鍵盤消失仍隱藏, 可點(diǎn)擊顯示
self.navigationController.hidesBarsWhenKeyboardAppears = YES;
// 如果自定義了返回按鍵, 則滑動(dòng)返回失能, 使用這行代碼繼續(xù)使能滑動(dòng)返回
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
/* --- navigationBar屬性 --- */
// 導(dǎo)航欄樣式
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
// 字體顏色
self.navigationController.navigationBar.tintColor = [UIColor cyanColor];
// 背景view顏色
self.navigationController.navigationBar.barTintColor = [UIColor purpleColor];
// 設(shè)置背景不透明
self.navigationController.navigationBar.translucent = NO;
// title樣式
NSShadow *shadow = [[NSShadow alloc] init];
shadow.shadowOffset = CGSizeMake(2.0, 2.0);
shadow.shadowColor = [UIColor grayColor];
NSDictionary *titleTextAttributes = @{NSFontAttributeName : [UIFont boldSystemFontOfSize:20], // 類型棱诱、大小
NSForegroundColorAttributeName : [UIColor redColor], // 顏色
NSShadowAttributeName : shadow}; // 陰影
self.navigationController.navigationBar.titleTextAttributes = titleTextAttributes;
/* --- toolbar屬性 --- */
// 工具欄樣式
self.navigationController.toolbar.barStyle = UIBarStyleBlack;
// 字體顏色
self.navigationController.toolbar.tintColor = [UIColor whiteColor];
// 背景view顏色
self.navigationController.toolbar.barTintColor = [UIColor brownColor];
/* --- 當(dāng)前viewController屬性 --- */
// title內(nèi)容
self.navigationItem.title = @"VC2 Title";
self.title = @"VC2";
NSLog(@"title:%p, nvItemTitle:%p", self.title, self.navigationItem.title); // 結(jié)論: 這倆是同一個(gè)對象
// 左邊按鈕 leftBarButtonItems
UIBarButtonItem *lBarBtnItem1 = [[UIBarButtonItem alloc] initWithTitle:@"lBtn1" style:UIBarButtonItemStylePlain target:self action:@selector(lAction1)];
UIBarButtonItem *lBarBtnItem2 = [[UIBarButtonItem alloc] initWithTitle:@"lBtn2" style:UIBarButtonItemStylePlain target:self action:@selector(lAction2)];
self.navigationItem.leftBarButtonItems = @[lBarBtnItem1, lBarBtnItem2];
// 右邊按鈕 rightBarButtonItems
UIBarButtonItem *barBtnItem1 = [[UIBarButtonItem alloc] initWithTitle:@"rBtn1" style:UIBarButtonItemStylePlain target:self action:@selector(rAction1)];
UIBarButtonItem *barBtnItem2 = [[UIBarButtonItem alloc] initWithTitle:@"rBtn2" style:UIBarButtonItemStylePlain target:self action:@selector(rAction2)];
self.navigationItem.rightBarButtonItems = @[barBtnItem1, barBtnItem2];
// 底部按鈕 toolbarItems
UIBarButtonItem *bBarBtnItem1 = [[UIBarButtonItem alloc] initWithTitle:@"bBtn1" style:UIBarButtonItemStylePlain target:self action:@selector(bAction1)];
UIBarButtonItem *bBarBtnItem2 = [[UIBarButtonItem alloc] initWithTitle:@"bBtn2" style:UIBarButtonItemStylePlain target:self action:@selector(bAction2)];
UIBarButtonItem *bBarBtnItem3 = [[UIBarButtonItem alloc] initWithTitle:@"bBtn3" style:UIBarButtonItemStylePlain target:self action:@selector(bAction3)];
UIBarButtonItem *spaceItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
self.toolbarItems = @[spaceItem, bBarBtnItem1, spaceItem, bBarBtnItem2, spaceItem, bBarBtnItem3, spaceItem]; // spaceItem自動(dòng)算出空格區(qū)間
其他方法屬性
topViewController & visibleViewController
UINavigationController有個(gè)visibleViewController屬性, 即當(dāng)前正在顯示的VC. 這個(gè)VC可以是push進(jìn)來或者present進(jìn)來的, 如果是push進(jìn)來的, 那么此VC同時(shí)也是topViewController; 如果是present進(jìn)來的, 那么topViewController不等于visibleViewController.
代理方法
// 即將展示視圖控制器時(shí)調(diào)用
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
// 已經(jīng)展示視圖控制器時(shí)調(diào)用
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
// 屏幕旋轉(zhuǎn)時(shí)泼橘,navigationController 支持的方向,多選
- (UIInterfaceOrientationMask)navigationControllerSupportedInterfaceOrientations:(UINavigationController *)navigationController NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED;
/** 子控制器支持的方向
* UIInterfaceOrientation 枚舉類型
* 1. UIInterfaceOrientationUnknown 設(shè)備的朝向不能確定迈勋。
* 2. UIInterfaceOrientationPortrait 該設(shè)備處于豎屏模式侥加,設(shè)備保持直立,底部的Home鍵粪躬。
* 3. UIInterfaceOrientationPortraitUpsideDown 該設(shè)備處于豎屏模式担败,但上下顛倒昔穴,設(shè)備保持直立,頂部的Home鍵提前。
* 4. UIInterfaceOrientationLandscapeLeft 設(shè)備處于橫向模式吗货,設(shè)備保持直立,右側(cè)Home鍵狈网。
* 5. UIInterfaceOrientationLandscapeRight 該設(shè)備處于橫向模式宙搬,設(shè)備保持直立,左側(cè)Home鍵拓哺。
*/
- (UIInterfaceOrientation)navigationControllerPreferredInterfaceOrientationForPresentation:(UINavigationController *)navigationController NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED;
返回按鍵
法一
修改系統(tǒng)返回按鍵的image, 不能添加文字
// 這兩個(gè)必須同時(shí)設(shè)置
self.navigationBar.backIndicatorImage = image;
self.navigationBar.backIndicatorTransitionMaskImage = image;
監(jiān)聽VC退出(點(diǎn)擊系統(tǒng)返回按鍵/pop操作)
- (void)viewWillDisappear:(BOOL)animated{
// 判斷 點(diǎn)擊系統(tǒng)返回按鍵/pop操作
if ([self.navigationController.viewControllers indexOfObject:self] == NSNotFound){
NSLog(@"%s", __func__);
}
[super viewWillDisappear:animated];
}
自定義返回按鈕, 原系統(tǒng)返回按鈕消失
/*法1*/
UIBarButtonItem *backBarBtnItem1 = [[UIBarButtonItem alloc] initWithTitle:@"back" style:UIBarButtonItemStylePlain target:self action:@selector(backAction)];
self.navigationItem.backBarButtonItem = backBarBtnItem1;
self.navigationController.interactivePopGestureRecognizer.delegate = nil; // 使能向右滑動(dòng)返回
/*法2*/
UIButton *back = [UIButton buttonWithType:UIButtonTypeCustom];
back.titleLabel.font = [UIFont boldSystemFontOfSize:13];
[back setTitle:@"Back" forState:UIControlStateNormal];
[back setFrame:CGRectMake(0, 0, 50, 30)];
[back addTarget:self action:@selector(backAction) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *barButton = [[UIBarButtonItem alloc] initWithCustomView:back];
self.navigationItem.leftBarButtonItem = barButton;
self.navigationController.interactivePopGestureRecognizer.delegate = nil; // 使能向右滑動(dòng)返回
法二
摘自https://github.com/onegray/UIViewController-BackButtonHandler
新建UIViewController 的 category.
.h文件
#import <UIKit/UIKit.h>
@protocol BackButtonHandlerProtocol <NSObject>
@optional
// Override this method in UIViewController derived class to handle 'Back' button click
- (BOOL)navigationShouldPopOnBackButton;
@end
@interface UIViewController (BackButtonHandler) <BackButtonHandlerProtocol>
@end
.m文件 (已更新適配iOS13, 詳情issues/13)
#import "UIViewController+BackButtonHandler.h"
#import <objc/runtime.h>
@implementation UIViewController (BackButtonHandler)
@end
@implementation UINavigationController (ShouldPopOnBackButton)
+ (void)load {
Method originalMethod = class_getInstanceMethod([self class], @selector(navigationBar:shouldPopItem:));
Method overloadingMethod = class_getInstanceMethod([self class], @selector(overloaded_navigationBar:shouldPopItem:));
method_setImplementation(originalMethod, method_getImplementation(overloadingMethod));
}
- (BOOL)overloaded_navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
if([self.viewControllers count] < [navigationBar.items count]) {
return YES;
}
BOOL shouldPop = YES;
UIViewController* vc = [self topViewController];
if([vc respondsToSelector:@selector(navigationShouldPopOnBackButton)]) {
shouldPop = [vc navigationShouldPopOnBackButton];
}
if(shouldPop) {
dispatch_async(dispatch_get_main_queue(), ^{
[self popViewControllerAnimated:YES];
});
} else {
// Workaround for iOS7.1. Thanks to @boliva - http://stackoverflow.com/posts/comments/34452906
for(UIView *subview in [navigationBar subviews]) {
if(0. < subview.alpha && subview.alpha < 1.) {
[UIView animateWithDuration:.25 animations:^{
subview.alpha = 1.;
}];
}
}
}
return NO;
}
@end
在用到的VC里面導(dǎo)入, 然后重寫方法
#import "UIViewController+BackButtonHandler.h"
- (BOOL)navigationShouldPopOnBackButton {
return NO; // YES or NO
}