一點說明:本文中“導航控制器”區別于“視圖控制器”存在
UINavigationController
UINavigationController
UINavigationController是一個導航控制器,它用來組織有層次關系的視圖。導航控制器維護著一個視圖控制器棧。在設計導航控制器時,UINavigationController默認也不會顯示任何視圖(這個控制器自身的UIView不會顯示),需要指定用戶看到的第一個視圖,該視圖控制器即是根控制器rootViewController,而且這個根控制器不會像其他子控制器一樣被銷毀,它是導航控制器棧中所有視圖控制器的棧底。在UINavigationController中子控制器以棧的形式存儲,只有在棧頂的控制器能夠顯示在界面中,一旦一個子控制器出棧則會被銷毀。
子控制器入棧出棧相關方法
官方文檔:You add and remove view controllers from the stack using segues or using the methods of this class. The user can also remove the topmost view controller using the back button in the navigation bar or using a left-edge swipe gesture.出棧:segue、method,移除棧頂:back button、left-edge swipe gesture
子控制器通過pop方法移除棧頂,先銷毀的是子控制器自己本身,然后子控制器里面的View才被銷毀,因為子控制器是持有View的。 導航控制下面的子控制器什么時候會被銷毀
- pop控制器,不會馬上銷毀棧頂控制器,而是告訴導航控制器需要把棧頂控制器出棧,等到恰當的時間就會把棧頂控制器出棧,并且銷毀
- initWithRootViewController:實際上是調用導航控制器的push方法
//Convenience method pushes the root view controller without animation. - 在子視圖中可以通過navigationController訪問導航控制器,同時可以通過navigationController的childViewControllers獲得當前棧中所有的子視圖(注意每一個出棧的子視圖都會被銷毀)
UINavigationBar與UINavigationItem的關系
UINavigationBar
UINavigationItem
UINavigationBar導航欄:繼承自UIView
*它最典型的用法就是放在屏幕頂端,包含著各級視圖的導航按鈕。它最首要的屬性是左按鈕(返回按鈕)、中心標題,還有可選的右按鈕(不過實際上UINavigationBar好像并沒有這些屬性,應該是存在于UINavigationItem類中的)。你可以單獨用導航欄,或者和導航控制器一起使用(后者最普遍)。
如果你使用導航控制器去管理不同屏幕內容之間的導航,導航控制器會自動創建NavigationBar,以及在合適的時候push\pop navigation items
UINavigationItem導航項:繼承自NSObject
一個UINavigationItem對象管理展示在導航欄上的按鈕和視圖。當創建一個導航界面的時候,每個壓入導航棧中的視圖控制器都需要一個navigation item,它包含了展示在導航欄上的按鈕和視圖。導航控制器利用最頂層的兩個視圖控制器的navigation item來提供導航欄的內容。(可以通過子視圖控制器(包括根視圖控制器)的navigationItem屬性訪問這個導航項,修改其左右兩邊的按鈕和標題的內容。)
- 關系:navigationcontroller直接控制viewcontrollers集合,然后它包含的navigationbar是整個工程的導航欄,bar有一個用來管理navigationItem的棧。
@property(nonatomic, copy) NSArray <UINavigationItem *> *items
navigationItem包含了navigationbar視圖的全部元素(如title,tileview,backBarButtonItem等),每個視圖控制器的導航項元素由所在視圖控制器的navigationItem管理。即設置當前頁面的左右barbutton,用 self.navigationItem.leftBarButtonItem等。
總結來說:navigationcontroller和navigationbar是一對一的關系,而navigationbar和navigationItem則是一對多的關系。
記錄一些需要注意的地方:
默認情況下除了根視圖控制器之外的其他子視圖控制器左側都會在導航欄左側顯示返回按鈕,點擊可以返回上一級視圖,同時按鈕標題默認為上一級視圖的標題,可以通過navigationItem的backBarButtonItem屬性修改。
leftBarButtonItem顯示原則:
1.如果當前的視圖控制器設置了leftBarButtonItem,則顯示當前VC所自帶的leftBarButtonItem。
2.如果沒有設置leftBarButtonItem,且不是根視圖控制器的時候,則顯示前一層的backBarButtonItem。如果前一層沒有指定backBarButtonItem的話,系統將會根據前一層的title屬性自動生成一個back按鈕,并顯示出來。
3.如果沒有設置leftBarButtonItem,且已是根視圖控制器的時候,左邊不顯示任何東西。下一級子視圖返回按鈕上的標題的顯示優先級為:前一層的backBarButtonItem的標題(注意不能直接給backBarButtonItem的標題賦值:即直接更改backBarButtonItem.title,只能重新給backBarButtonItem賦值)、前一層navigationItem的標題,前一層視圖控制器標題。
-
UINavigationController沒有navigationItem這樣一個直接的屬性,但由于UINavigationController繼承于UIViewController,它有navigationItem這個屬性
因此在視圖控制器中這樣寫self.navigationController.navigationItem.title = @"friend";
是沒問題的,但是實際上并不能這樣設置標題(self.navigationController.navigationItem 是應該被忽視的屬性)。由于UINavigationController是視圖控制器的容器,雖然它是特殊的視圖控制器,但不應該把它當一般的UIViewController來使用.如果要設置視圖控制器標題,應該這樣寫:self.navigationItem.title = @"friend";
navigationItem是UIViewController的屬性,當第一次訪問視圖控制器的這個屬性的時候,它會被創建。(不太明白)
設置標題:
self.navigationItem.title = @"friend";
改變的是當前視圖控制器的標題。(備注:self.navigationItem.title和self.title的效果是一樣的)
設置導航欄顏色:self.navigationController.navigationBar.barTintColor = [UIColor redColor];
改變的是所有視圖控制器的導航欄顏色
單獨用導航欄UINavigationBar,(不是采用UINavigationController)
代碼方式:
- (void)viewDidLoad{
UINavigationBar *navBar = [[UINavigationBar alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 44)];
//添加NavigationBar到視圖上
[self.view addSubview:navBar];
UINavigationItem *navItem = [[UINavigationItem alloc]initWithTitle:@"welcome page"];
UIBarButtonItem *btnlogin = [[UIBarButtonItem alloc]initWithTitle:@"login" style:UIBarButtonItemStyleDone target:self action:@selector(login)];
navItem.leftBarButtonItem = btnlogin;
//把NavigationItem添加到導航欄上(進棧)
[navBar pushNavigationItem:navItem animated:NO];
}
UIBarButtonItem
UIBarButtonItem繼承自UIBarItem,再往上的繼承NSObject
是專門放在UIToolbar or UINavigationBar上的控件,具有按鈕的行為。它分左、右、返回UIBarButtonItem,可以把她們添加到UINavigationItem上去
UINavigationItem *navItem = [[UINavigationItem alloc]initWithTitle:@"welcome page"];
UIBarButtonItem *btnlogin = [[UIBarButtonItem alloc]initWithTitle:@"login" style:UIBarButtonItemStyleDone target:self action:@selector(login)];
navItem.leftBarButtonItem = btnlogin;
UIBarButtonItem有如下初始化方法:
第一種可以設置系統自帶風格按鈕UIBarButtonSystemItem。
1.使用代碼方式創建導航
步驟:
- 初始化UINavigationController
- 設置UIWindow的rootViewController為UINavigationController
- 通過push添加對應子控制器
1.friend視圖控制器,設置左右導航欄按鈕
#import <UIKit/UIKit.h>
@interface FriendViewController : UIViewController
@end
#import "FriendViewController.h"
#import "ContactViewController.h"
@interface FriendViewController ()
@end
@implementation FriendViewController
- (void)viewDidLoad {
[super viewDidLoad];
//根據實際運行結果看來(視圖不斷切換,但下面這句話只會打印一次):
//因為是根視圖控制器,永遠不會被銷毀,所以viewdidload只會執行一次。
NSLog(@"childviewcontroller:%@",self.navigationController.childViewControllers);
NSLog(@"%i",self.navigationController == self.parentViewController);//true
self.navigationItem.title = @"friend";
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"edit" style:UIBarButtonItemStyleDone target:nil action:nil];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"User_normal"] style:UIBarButtonItemStyleDone target:self action:@selector(addPeople)];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)addPeople{
ContactViewController *contactVC = [[ContactViewController alloc]init];
[self.navigationController pushViewController:contactVC animated:YES];
}
@end
- 不設置渲染模式,默認情況下系統會把導航欄上按鈕的圖片渲染成藍色
2.contact視圖控制器,添加右導航按鈕,左導航按鈕不設置默認顯示上級視圖控制器名稱作為返回按鈕
#import <UIKit/UIKit.h>
@interface ContactViewController : UIViewController
@end
#import "ContactViewController.h"
#import "AccountViewController.h"
@interface ContactViewController ()
@end
@implementation ContactViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 是子控制器,在視圖不斷切換的過程中,反復被創建和銷毀;下面這句話多次打印出來。
NSLog(@"childviewcontroller:%@",self.navigationController.childViewControllers);
NSLog(@"%i",self.navigationController == self.parentViewController);
self.view.backgroundColor = [UIColor whiteColor];//***注意***不設置切換會有卡頓
[self setTitle:@"contact"];
//設置下一級視圖控制器導航返回按鈕
UIBarButtonItem *back = [[UIBarButtonItem alloc]initWithTitle:@"my contact" style:UIBarButtonItemStyleDone target:nil action:nil];
self.navigationItem.backBarButtonItem = back;
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"account" style:UIBarButtonItemStyleDone target:self action:@selector(goToAccount)];
}
- (void)goToAccount{
AccountViewController *accountVC = [[AccountViewController alloc]init];
[self.navigationController pushViewController:accountVC animated:YES];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
#import <UIKit/UIKit.h>
@interface AccountViewController : UIViewController
@end
#import "AccountViewController.h"
#import "FriendViewController.h"
@interface AccountViewController ()
@end
@implementation AccountViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"account";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"toFriend" style:UIBarButtonItemStyleDone target:self action:@selector(goToFriend)];
}
- (void)goToFriend{
//直接跳轉到根控制器
[self.navigationController popToRootViewControllerAnimated:YES];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
4.初始化導航控制器并設置根視圖控制器
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor whiteColor];
[[UINavigationBar appearance]setBarTintColor:[UIColor yellowColor]];
[[UINavigationBar appearance]setBarStyle:UIBarStyleBlack];
FriendViewController *friendVC = [[FriendViewController alloc]init];
UINavigationController *navController = [[UINavigationController alloc]initWithRootViewController:friendVC];
self.window.rootViewController = navController;
[self.window makeKeyAndVisible];
return YES;
}
*需要注意的地方:在使用UINavigationController的pushViewController:animated:執行入棧一個子控制器操作時,會出現"卡頓"現象。
原因:這是因為從iOS7開始, UIViewController的根view的背景顏色默認為透明色(即clearColor),所謂"卡頓"其實就是由于透明色重疊后,造成視覺上的錯覺,所以這并不是真正的"卡頓"。
解決方法:只要在該UINavigationController所push的那個子控制器中設置背景顏色,即取締默認的透明色 (即clearColor)
如:在viewDidLoad中寫上 self.view.backgroundColor = [UIColor whiteColor];
- 跳轉到指定控制器:
// 注意:跳轉到指定控制器的時候,要跳轉到的目標控制器必須是在當前導航控制器的棧內的,不能是新創建的控制器
NSArray *vcs = self.navigationController.childViewControllers;
OneViewController *oneVc = (OneViewController *)vcs[1];
[self.navigationController popToViewController:oneVc animated:YES];
}
** 注意:返回到指定控制器的時候,要跳轉到的目標控制器必須是在當前導航控制器的棧內的,不能是新創建的控制器**
2.使用storyboard創建導航
1.在storyboard中拖拽一個UINavigationController。UINavigationController默認會帶一個UITableViewController作為其根控制器。設置UITableViewController的標題為“Testing”,同時設置為靜態表格并且包含兩行,分別在單元格中放置一個UILabel命名為“A”和“B”
2.新建兩個UITableViewController,標題分別設置為“A”、“B”
按住Ctrl拖拽“ Testing”的第一個表單元格到視圖控制器“A”,同時選擇segue為“show”,拖拽第二個表單元格到視圖控制器“B”,同時選擇segue為“show”。
到這里為止,可以通過點擊兩個單元格導航到A、B視圖。
這里通過storyboard創建導航的關鍵是"segue"
Segue的工作方式分為以下幾個步驟:
1.創建目標視圖控制器(即A、B視圖控制器)
2.創建Segue對象
3.調用源視圖對象的prepareForSegue:sender:方法
4.調用Segue對象的perform方法將目標視圖控制器推送到屏幕
5.釋放Segue對象
先給Testing控制器綁定一個控制器文件(繼承于UITableViewController)。然后選中連接A控制器的segue,設置它的identifier為"Segue",B同理
在Testing控制器對應文件中添加:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
// 獲得源視圖控制器
UITableViewController *testingVC = segue.sourceViewController;
//獲得目標視圖控制器
UITableViewController *desVC = segue.destinationViewController;
NSLog(@"sourceController:%@,destinationController:%@",testingVC.navigationItem.title,desVC.navigationItem.title);
}
點擊兩個單元格,出現打印結果如下:對應上述第3步
在Testing視圖控制器中放上左右導航按鈕,并添加對應點擊事件
- (IBAction)toA:(id)sender {
[self performSegueWithIdentifier:@"ASegue" sender:self];
}
- (IBAction)toB:(id)sender {
[self performSegueWithIdentifier:@"BSegue" sender:self];
}
運行程序,點擊左右兩個導航按鈕,同樣可以跳轉到對應的A、B視圖控制器,對應上述第4步。
什么是Segue(延伸)
Storyboard上每一根用來界面跳轉的線,都是一個UIStoryboardSegue對象(簡稱Segue)
-
segue的類型
根據Segue的執行(跳轉)時刻,可分為2大類型
1.自動型:點擊某個控件后(比如按鈕),自動執行Segue,自動完成界面跳轉(按住Control鍵,直接從控件拖線到目標控制器)
2.手動型:需要通過寫代碼手動執行Segue,才能完成界面跳轉(按住Control鍵,從來源控制器拖線到目標控制器)
創建手動型segue
手動型的Segue需設置標識identifier,在需要的時刻,由來源控制器執行perform方法調用對應的Segue[self performSegueWithIdentifier:@"填入segue的標識" sender:nil];
performSegueWithIdentifier:sender:的執行過程
[self performSegueWithIdentifier:@"xxx" sender:nil];
1.self是來源控制器,只能通過來源控制器來調該方法。
2.根據identifier去storyboard中找到對應的線,新建UIStoryboardSegue對象
3.設置Segue對象的sourceViewController(來源控制器)
4.新建并且設置Segue對象的destinationViewController(目標控制器)
5.調用- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法。(prepare方法里的sender參數是調用perform方法時sender傳入的對象)
6.執行跳轉。