多控制器切換.
1.概述.
在iOS開發(fā)中,視圖的切換是很頻繁的,常用的視圖切換如下:
-
UITabBarController的Tab切換
- 以平行的方式管理視圖,各個(gè)視圖之間關(guān)系不大;每個(gè)加入的視圖都會(huì)進(jìn)行初始化,不論當(dāng)前顯不顯示在界面上.所以相對比較占內(nèi)存.
-
UINavigationController 的 push和pop.
- 以棧的方式管理視圖,各個(gè)視圖的切換就是壓棧和出棧操作,出棧后的視圖即被銷毀.
-
modal模態(tài)窗口.
- 以模態(tài)窗口的形式管理視圖presentation出現(xiàn)和dismissal關(guān)閉逃魄,當(dāng)前視圖關(guān)閉前其他視圖上的內(nèi)容無法操作糊肠。(遮蓋)
- UICollectionViewController的布局轉(zhuǎn)場,僅限于UICollectionViewController與UINavigationController結(jié)合的轉(zhuǎn)場方式.
- 自定義控制器切換.
2. UINavigationController
2.1 簡介
導(dǎo)航控制器,用來組織有層次關(guān)系的視圖饼问,在UINavigationController中子控制器以棧(先進(jìn)后出)的形式存儲(chǔ),只有在棧頂?shù)目刂破髂軌蝻@示在界面中卖陵,一旦一個(gè)子控制器出棧則會(huì)被銷毀;它必須有指定一個(gè)根控制器rootViewController才能創(chuàng)建吴藻,而且這個(gè)根控制器不會(huì)像其他子控制器一樣被銷毀,剛創(chuàng)建時(shí),rootViewController即是棧底也是棧頂控制器;
導(dǎo)航條:
導(dǎo)航條的設(shè)置是根據(jù)棧頂控制器的navigationItem屬性設(shè)置,導(dǎo)航條子控件是系統(tǒng)決定位置.高度44;
基本步驟:
//1. 創(chuàng)建導(dǎo)航控制器
UINavigationController nav = [[UINavigationController alloc] initWithRootViewController:rootViewController];
//2. 添加子控制器,子控制器表現(xiàn)兩種存儲(chǔ)形式:viewControllers 和 childViewController數(shù)組.添加方式如下:
// nav.viewControllers = @[vc,vc2];
//[nav addChildViewController:vc];
//initWithRootViewController:vc;
//3. 跳轉(zhuǎn)-進(jìn)棧
[nav pushViewController:vc animated:YES]; //此方法默認(rèn)封裝了添加子控件方法.所以可以省略第二步
//4. 跳轉(zhuǎn)-出棧
a. 返回上一個(gè)(棧頂出棧)popViewControllerAnimated: ;
b. 返回根控制器(出棧至棧底)popToRootViewControllerAnimated: ;
c. 返回指定控制器(根據(jù)存儲(chǔ)形式下標(biāo)).popToViewController: .
2.2 使用范例:
AppDelegate.m中:
//創(chuàng)建窗口
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//1. 創(chuàng)建導(dǎo)航控制器的根控制器
ViewController *vc = [[ViewController alloc] init];
vc.view.backgroundColor = [UIColor redColor];
//2. 創(chuàng)建導(dǎo)航控制器
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
nav.view.backgroundColor = [UIColor blueColor];
//引申:這里可以設(shè)置全局導(dǎo)航條的風(fēng)格和顏色
[[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1]];
[[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];
//3.把導(dǎo)航控制器設(shè)為窗口根控制器
self.window.rootViewController = nav;
//設(shè)為主窗口并顯示
[self.window makeKeyAndVisible];
在子控制器ViewController中
//對于當(dāng)前子視圖來說其父控制器就是器導(dǎo)航控制器,可以由此獲取.
//self.navigationController == self.parentViewController;
//在子視圖中,可以通過navigationItem用于訪問和設(shè)置導(dǎo)航條信息. (屏幕正在顯示的導(dǎo)航條信息就是棧頂子控制器的title)
self.navigationItem.title = @"haha"; //可以用self.title 代替 是系統(tǒng)內(nèi)部封裝的快速設(shè)置標(biāo)題方法.
//例:設(shè)置導(dǎo)航條左側(cè)按鈕
//方式一: 新建一個(gè)
UIButton *button = [[UIBarButtonItem alloc] init];
//給button設(shè)置圖片,title等屬性.
//導(dǎo)航條上子控件的位置是由系統(tǒng)決定的, 但是尺寸是由我們自己決定的.可以設(shè)置bounds;無特殊要求可以使用自適應(yīng)方法.
[button sizeToFit];
//最后,根據(jù)這個(gè)button來自定義創(chuàng)建導(dǎo)航條按鈕.
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:button];
//方式二: 直接調(diào)用系統(tǒng)創(chuàng)建方法,根據(jù)style選擇不同風(fēng)格
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"Icon.png"] style:UIBarButtonItemStyleDone target:self action:@selector(addFriends)];
}
//創(chuàng)建并轉(zhuǎn)場下一個(gè)控制器
-(void)addFriends{
UIViewController *vc=[[UIViewController alloc]init];
self.hidesBottomBarWhenPushed = YES; // 隱藏底部tabBar.
[self.navigationController pushViewController: vc animated:YES];
}
//iOS 7 之后, 默認(rèn)會(huì)把導(dǎo)航條上的按鈕的圖片渲染成藍(lán)色,可取消自動(dòng)渲染
//image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
下一級子控制器中,如果要返回.調(diào)用下面方法即可
a. 返回上一個(gè)(棧頂出棧)popViewControllerAnimated: ;
b. 返回根控制器(出棧至棧底)popToRootViewControllerAnimated: ;
c. 返回指定控制器(根據(jù)存儲(chǔ)形式下標(biāo)).popToViewController: .
2.3 小結(jié):
- UINavigationController默認(rèn)顯示一個(gè)根控制器視圖,所以必須指定rootViewController;
- 子視圖中可以通過navigationController來訪問導(dǎo)航控制器,它的childViewControllers來獲取當(dāng)前棧中所有子視圖.(出棧的已被銷毀)
- 導(dǎo)航控制器導(dǎo)航條由棧頂控制器設(shè)置
- 默認(rèn)情況下除了根控制器之外的其他子控制器左側(cè)都會(huì)在導(dǎo)航欄左側(cè)顯示返回按鈕,點(diǎn)擊可以返回上一級視圖卑惜,同時(shí)返回按鈕的title默認(rèn)是上一級的標(biāo)題. 可以通過backBarButtonItem屬性修改;
3. UITabBarController
3.1 簡介
UITabBarController是蘋果專門為了利用頁簽切換視圖而設(shè)計(jì)的,包含一個(gè)UITabBar控件,用戶通過點(diǎn)擊tabBar進(jìn)行視圖切換.為了盡可能減少視圖之間的耦合,所有UITabBarController的子視圖的相關(guān)標(biāo)題驻售、圖標(biāo)等信息均由各自子視圖自己控制露久,UITabBarController僅僅作為一個(gè)容器存在。
結(jié)構(gòu):
和導(dǎo)航控制器類似:它的自帶view用來存放導(dǎo)航條UITabbar和子控制器view兩部分;
不同點(diǎn):
- 它的導(dǎo)航條UITabbar在下方,高度49;
- 不依賴RootViewController創(chuàng)建;
- UITabbar的子控件UITabBarButton樣式跟棧頂控制器無關(guān),只跟對應(yīng)的子控制器有關(guān).子控件數(shù)由子控制器數(shù)決定.位置是自動(dòng)均分的,所以一般分四個(gè),典型例子微信和QQ.
- 導(dǎo)航控制器出棧會(huì)銷毀子控制器,UITabBarController不會(huì).
3.2 一般步驟
- 新建初始化UITabbarController;并設(shè)為窗口的root控制器.
- 設(shè)置UITabBarButton樣式:
- 由對應(yīng)子控制器的UITabBarItem設(shè)置.
- 包含:title標(biāo)題,image圖標(biāo),selectedImage選中狀態(tài)圖標(biāo),badgeValue右上角內(nèi)容通知個(gè)數(shù);(iOS7之后系統(tǒng)自動(dòng)渲染)
- 添加 子控制器方式.
- [tb addChildViewController:c1];
- tb.viewControllers=@[c1,c2,c3,c4];
- 跳轉(zhuǎn):點(diǎn)擊UITabBarButton自動(dòng)跳轉(zhuǎn).
3.3 小結(jié)
- UITabBarController會(huì)一次性初始化所有子控制器,所以可以將視圖控制器的tabBarItem屬性設(shè)置放到init方法中設(shè)置.
- 每個(gè)視圖控制器都有一個(gè)tabBarController屬性欺栗,通過它可以訪問所在的父控件UITabBarController.
- 每個(gè)視圖控制器都有一個(gè)tabBarItem屬性毫痕,通過它控制視圖在UITabBarController的tabBar中的顯示信息。
4. 模態(tài)窗口.
4.1 簡介
模態(tài)窗口只是視圖控制器的顯示的一種方式.不依賴與控制器容器;通常用于顯示較獨(dú)立的內(nèi)容,在模態(tài)窗口顯示的時(shí),其他視圖的內(nèi)容無法進(jìn)行操作.
4.2 一般使用
使用起來比較容易,一般的視圖控制器只要調(diào)用- (void)presentViewController:(UIViewController *)viewController animated: (BOOL)flag completion:(void (^)(void))completion
方法, 那么參數(shù)中的viewController就會(huì)以模態(tài)窗口的形式展現(xiàn); 而此視圖控制器再調(diào)用---(void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^)(void))completion
就會(huì)關(guān)閉模態(tài)窗口,回到原視圖.
注意:modal出誰,誰才可以使用dismiss;
一般為了操作方便,會(huì)手動(dòng)給模態(tài)窗口設(shè)置導(dǎo)航條,兩種方式:
第一種:手動(dòng)創(chuàng)建
//創(chuàng)建一個(gè)導(dǎo)航欄
UINavigationBar *navigationBar=[[UINavigationBar alloc]initWithFrame:CGRectMake(0, 0, 320, 44+20)];
//navigationBar.tintColor=[UIColor whiteColor];
[self.view addSubview:navigationBar];
//創(chuàng)建導(dǎo)航控件內(nèi)容
UINavigationItem *navigationItem=[[UINavigationItem alloc]initWithTitle:@"Web Chat"];
//左側(cè)添加登錄按鈕
_loginButton=[[UIBarButtonItem alloc]initWithTitle:@"登錄" style:UIBarButtonItemStyleDone target:self action:@selector(login)];
navigationItem.leftBarButtonItem=_loginButton;
//添加內(nèi)容到導(dǎo)航欄
[navigationBar pushNavigationItem:navigationItem animated:NO];
第二種: 把控制器包裝成導(dǎo)航控制器的root控制器.這是給控制器添加導(dǎo)航條的最快方法.
補(bǔ)充1:目前APP的主流框架
導(dǎo)航控制器和UITabBarController結(jié)合.一般由UITabBarButton做父控件:
原因:
- 由于導(dǎo)航控制器的導(dǎo)航條由棧頂控制器決定,如果導(dǎo)航做父控制器,那么在UITabBarController的子控件間切換時(shí),上方導(dǎo)航始終不變.而UITableBarController的bar由各子控制器決定自己的.
- UITabBarController的子控制器數(shù)目由于均分UITabBar位置,數(shù)目有限制,一般不超過五個(gè).
補(bǔ)充2:使用Storyboard 工作時(shí)的切換
上面所說的控制器切換的觸發(fā)方式主要有三種,包括:代碼里調(diào)用相關(guān)方法, UINavigationBar和UITabBar自帶的item點(diǎn)擊操作, 最后一個(gè)就是Storyboard的Segue方式.如果你使用Storyboard進(jìn)行開發(fā),就需要了解一下.
segue跳轉(zhuǎn)原理.
如果segue的type是push的,那么系統(tǒng)是取得sourceViewController所在的UINavigationViewController, 再調(diào)用push方法壓入棧中完成跳轉(zhuǎn).
如果segue的type是Modal的,那么系統(tǒng)會(huì)調(diào)用sourceViewController的presentViewController
方法,將destinationViewController展示出來.界面跳轉(zhuǎn)步驟:
在 storyboard 里設(shè)置 segue有兩種方式:Button to VC迟几,這種在點(diǎn)擊 Button 的時(shí)候觸發(fā)轉(zhuǎn)場消请;VC to VC,這種需要在代碼中調(diào)用performSegueWithIdentifier:sender:
类腮。
prepareForSegue:sender:
方法是在轉(zhuǎn)場發(fā)生前修改轉(zhuǎn)場參數(shù)的最后機(jī)會(huì)臊泰。這點(diǎn)對于 Modal 轉(zhuǎn)場比較重要,因?yàn)樵?storyboard 里 Modal 轉(zhuǎn)場的 Segue 類型不支持選擇 Custom 模式存哲,使用 segue 方式觸發(fā)時(shí)必須在prepareForSegue:sender:
里修改模式因宇。控制器間值的傳遞:
//1 .來源控制器把數(shù)據(jù)->目標(biāo)控制器.代碼示例:
//sourceViewController中;
[self performSegueWithIdentifier:@"login2Contact" sender:nil];
//此方法跳轉(zhuǎn)前會(huì)執(zhí)行prepareForSegue方法
//并傳入sugue 和sender :可通過傳入的segue獲取來源和目標(biāo)控制器; 而sender是之前傳入的nil;
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// 獲取目標(biāo)控制器.
UIViewController *vc = segue.destinationViewController;
// 順傳: 上一個(gè)控制器(來源控制器)把數(shù)據(jù)傳遞給下一個(gè)控制器(目標(biāo)控制器) 很簡單,直接傳遞即可.
vc.navigationItem.title = self.accountField.text;
}//可以傳遞模型數(shù)據(jù).在目標(biāo)控制器中設(shè)置屬性接收即可.
// 2. 逆?zhèn)髦?
原理是:來源控制器把自己傳給目標(biāo)控制器,再在我們目前所在的目標(biāo)vc中設(shè)置來源vc的屬性. **但是:這種方式耦合性太高,所以引入delegate解耦;**
//代理:有限制的對象間關(guān)聯(lián)關(guān)系,通過把兩個(gè)關(guān)聯(lián)對象用協(xié)議束縛起來,達(dá)到解耦目的.
1. 來源VC 做 目標(biāo)VC 的代理,我們在目標(biāo)VC中定義協(xié)議,聲明方法(把自己和數(shù)據(jù)作為參數(shù))一般只傳數(shù)據(jù)就夠了,但是傳自己是蘋果delegate編程習(xí)慣. 來源VC來 遵守協(xié)議,實(shí)現(xiàn)方法.
2. 通過segue在目標(biāo)VC中獲取來源VC.調(diào)用此方法,傳入數(shù)據(jù).
3. **注意1:**調(diào)用前先做個(gè)判斷(是否代理實(shí)現(xiàn)了方法,避免崩潰) respondsToSelect;
4. **注意2:**如果一個(gè)控制器segue了多個(gè)控制器,那么用segue獲取目標(biāo)控制器需要判斷一下- isKindOfClass:
5. **注意3:**控制器順傳通常不能通過重寫模型數(shù)據(jù)的set方法給子控件賦值,因?yàn)檫@個(gè)傳值是在跳轉(zhuǎn)之前傳得,而此時(shí)控制器的view還沒有加
載,也就意味著子控件還沒創(chuàng)建. 所以把值傳過去后,一般在viewdidload中設(shè)置子控件屬性.
6. 其他逆?zhèn)髦捣绞?
* block逆?zhèn)髦?捕獲自動(dòng)變量的匿名函數(shù)指針.
* 通知機(jī)制
* 通過文件存儲(chǔ)
* 通過AppDelegate定義全局變量(或者使用UIApplication、定義一個(gè)單例類等)
//代理來逆?zhèn)髦?代碼如下:
//目標(biāo)VC:
// .h中 定義協(xié)議
@protocol HMAddViewControllerDelegate <NSObject>
// 聲明方法.
- (void)addViewControllerWith:(HMAddViewController *)addVc didClickButton:(HMContact *)contact; //contact是模型數(shù)據(jù)
// 新增delegate
@property (nonatomic, weak) id<HMAddViewControllerDelegate> delegate;
//.m中
// 通知代理: 聯(lián)系人控制器
if ([self.delegate respondsToSelector:@selector(addViewControllerWith:didClickButton:)]) {
[self.delegate addViewControllerWith:self didClickButton:contact];
}
來源VC中.
//1. 在prepareForSegue方法中獲取到目標(biāo)控制器 . 讓自己成為其代理
// 獲取目標(biāo)控制器(添加控制器)
HMAddViewController *addVc = segue.destinationViewController;
// 傳遞聯(lián)系人控制器: 給目標(biāo)控制器的 contactVc 屬性賦值
addVc.delegate = self;
//2. 實(shí)現(xiàn)代理方法
- (void)addViewControllerWith:(HMAddViewController *)addVc didClickButton:(HMContact *)contact {
// 保存聯(lián)系人模型
[self.contacts addObject:contact];
}
5.自定義多控制器切換.
最后,很多時(shí)候,系統(tǒng)的導(dǎo)航控制器和UITabBarController并不能滿足項(xiàng)目需求, 這時(shí)就需要我們自定義跳轉(zhuǎn)效果.
1. 實(shí)現(xiàn)原理.
其實(shí)很簡單:只需在主控制器上創(chuàng)建新子控制器,并讓這個(gè)子控制器的View覆蓋主View即可;
2. 細(xì)節(jié)與步驟.
- 創(chuàng)建新控制器,并將所有新控制器 變?yōu)橹骺刂破鞯淖涌刂破?
[self addChildViewController:[[OneViewController alloc] init]];
[self addChildViewController:[[TwoViewController alloc] init]];
- 在主控制器中使用屬性記錄正在顯示新控制器,方便使用.(由于前面都已經(jīng)add,所以這里可以使用weak)
@property (nonatomic, weak) UIViewController *showingChildVc;
- 提供切換控制器方法, 為了減少代碼冗余, 可以使用下標(biāo)來訪問 childViewcontrollers數(shù)組.
-(void)switchVC:(int)index { //即將要顯示的子控制器索引
//1. 移除當(dāng)前正在顯示的其他子控制器的view
[self.showingChildVc.view removeFromSuperview];
//2. 添加index位置對應(yīng)新控制器的view,并設(shè)置frame,
UIViewController *newVc = self.childViewcontrollers[index];
newVc.view.frame = CGRectMake(0, 44, self.view.frame.size.width, self.view.frame.size.height - 44);
[self.view addSuperview:newVc.view];
//3. 重新記錄要顯示的子控制器
self.showingChildVc = newVc;
}
注意: 一定要建立需切換控制器的父子關(guān)系, 否則某些系統(tǒng)事件 子控制器將無法接收并響應(yīng). 也無法獲取父控制器的tabbar或?qū)Ш娇刂破靼l(fā)送跳轉(zhuǎn)消息.
自定義界面切換的方式和選擇:
- 業(yè)務(wù)邏輯簡單: 一個(gè)控制器多個(gè)View 切換即可
- 業(yè)務(wù)邏輯復(fù)雜時(shí) 使用多個(gè)控制器多個(gè)view,建立父子關(guān)系切換.(相當(dāng)于一個(gè)view配一個(gè)控制器,方便管理)
補(bǔ)充:使用系統(tǒng)類型的轉(zhuǎn)場動(dòng)畫.
- 自定義動(dòng)畫代碼因添加在切換控制器的方法中.
- 執(zhí)行過渡動(dòng)畫的view要經(jīng)歷移除和添加事件.
- 動(dòng)畫執(zhí)行調(diào)用CATransition類核心動(dòng)畫, 這個(gè)核心動(dòng)畫是添加在View的圖層layer上的.示例代碼:
- (void)switchVc:(int)index
{
UIViewController *newVc = self.childViewControllers[index];
// 如果index對應(yīng)的子控制器正在顯示,就直接返回
if (newVc == self.showingChildVc) return;
// 0.保存新舊控制器的索引
NSUInteger newIndex = index;
NSUInteger oldIndex = [self.childViewControllers indexOfObject:self.showingChildVc];
// 1.移除其它控制器的view
[self.showingChildVc.view removeFromSuperview];
// 2.添加index位置對應(yīng)控制器的view
newVc.view.frame = self.contentView.bounds;
[self.contentView addSubview:newVc.view];
self.showingChildVc = newVc;
// 3.執(zhí)行動(dòng)畫,可根據(jù)subtype類型選擇不同動(dòng)畫方式.
if (oldIndex == NSNotFound) return;
CATransition *animation = [CATransition animation];
animation.type = @"cube";
if (newIndex > oldIndex) {
animation.subtype = kCATransitionFromRight;
} else {
animation.subtype = kCATransitionFromLeft;
}
animation.duration = 1.0;
[self.contentView.layer addAnimation:animation forKey:nil];
//3. 也可使用UIView封裝的動(dòng)畫,自己設(shè)計(jì) .
// 動(dòng)畫
/*
[UIView animateWithDuration:0.5 animations:^{
CGRect oldFrame = self.showingChildVc.view.frame;
oldFrame.origin.x = - self.view.frame.size.width;
self.showingChildVc.view.frame = oldFrame;
newVc.view.frame = self.contentView.bounds;
}completion:^(BOOL finished) {
[self.showingChildVc.view removeFromSuperview];
self.showingChildVc = newVc;
}];
*/
}
補(bǔ)充: 完全自定義轉(zhuǎn)場動(dòng)畫