對于初學(xué)者來說往往無法弄清iOS中各種各樣的Controller之間的關(guān)系和使用場景。這篇文章將嘗試整理各個Controller的用法以及注意事項柠衅。
主要介紹的Controller有:
- UIViewController
- UINavigationController
- UITabBarController
- ModalViewController
- ChildViewController
UIXXXController之間的關(guān)系
對于各個Controller之間的關(guān)系我想從下圖開始講解:
程序的入口是main()這個大家都知道待侵,iOS也不例外也是在main
里面:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
對于這個main里面干了什么事情與這篇文章的主題不太符合斯辰,初學(xué)者可能也不太能理解八回,感興趣的可以去了解一下runloop
,我們只要知道在這之后扇商,我們的APP將要執(zhí)行的類就到了AppDelegate,并且我們的代碼也是從這里開始宿礁。
與我們今天主題最相關(guān)的就是下面一段了:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.rootViewController = [[UIViewController alloc] init];
[self makeKeyAndVisible];
return YES;
}
這段代碼設(shè)置就是iOS程序里啟動后顯示的第一個ViewController案铺,接下來的流程就全由你自己控制了。
PS:在Xcode5 之后默認(rèn)使用storyboard不需要在這里寫任何代碼就會有顯示默認(rèn)的ViewController梆靖,那時因為在info.plist中選擇使用Main.storyboard的initialViewController作為初始ViewController控汉。
對于一個iOS的APP來講第一個UIViewController的類型是非常重要的,它將決定這個APP的UI架構(gòu)和層級返吻。
我們就從Xcode自帶的3個典型模板工程去分析和講解姑子。
如圖,Xcode創(chuàng)建工程時有幾組模板工程可以選擇:Master-Detail Application
测僵、Single View Application
街佑、Tabbed Application
。其中創(chuàng)建后對應(yīng)的rootViewController分別為:
- Master-Detail Application ---> UINavigationController
- Single View Application ---> UIViewController
- Tabbed Application ---> UITabbarController
PS:Page-Based Application在今天的主題中暫不討論
UINavigationController
使用方法
//初始化
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[CustomViewController new]];
//在vc中使用
[self.navigationController pushViewController:vc animated:YES];
視圖跳轉(zhuǎn)
UINavigationController實際可以理解為UIViewController的一個棧捍靠,有著先進(jìn)后出的特點沐旨,操作過程中我們一般也是使用的push和pop進(jìn)行操作。
pushViewController:animated:
popViewControllerAnimated:
popToRootViewControllerAnimated:
popToViewController:animated:
在這一塊需要注意的是我們使用的self.navigationController剂公,在實際使用中我們都應(yīng)該是在UIViewController(UINavigationController棧里面的)來調(diào)用該方法希俩,這個屬性值最初就是在setRootController時系統(tǒng)設(shè)置的,然后在每一次push過程中傳遞纲辽。然后在push和pop這一塊沒什么好說的了颜武。
視圖區(qū)域
如圖,紅色線框區(qū)域為navigationController.view的范圍區(qū)域拖吼,藍(lán)色區(qū)域為navigationController當(dāng)前棧頂?shù)腢IViewController的視圖區(qū)域鳞上,黃色區(qū)域就是后面我們要講的NavigationBar的區(qū)域。
由圖可以看出UIViewController是處于UINavigationController內(nèi)吊档,并覆蓋在navigationController.view上的篙议,在我們push和pop操作過程中實際上更改的內(nèi)容僅僅是UIViewController區(qū)域的內(nèi)容,所以你在任何棧內(nèi)的viewController使用navigationController.view操作,之后的結(jié)果是不會隨著push和pop改變的鬼贱。
在這里你還需要注意UINavigationController的幾個屬性:
topViewController //返回UINavigationController棧頂?shù)膙iewController
visibleViewController //返回UINavigationController可見到的viewController 包括ModalViewController
viewControllers //返回UINavigationController棧里面的所有viewController移怯,以NSArray形式返回
topViewController與visibleViewController區(qū)別在于,如果當(dāng)前棧頂?shù)氖莢iewController1这难,然后在這個Controller中使用presentViewController以Modal方式彈出viewController2舟误。topViewController返回的是viewController1,visibleViewController返回的則是viewController2姻乓。
UITabbarController
對于UITabbarController這就沒什么好說的了嵌溢,使用就真的是簡單。
UITabBarController *tabBarViewController = [[UITabBarController alloc]init];
[self.window setRootViewController:tabBarViewController];
FirstViewController* first = [[FirstViewController alloc]init];
SecondViewController* second = [[SecondViewController alloc]init];
tabBarViewController.viewControllers = [NSArray arrayWithObjects:first, second,nil];
更詳細(xì)信息見:官方文檔
ChildViewController
今天的重點在于這一塊了蹋岩,首先我們拋出一個問題:
如何在一個ViewController中創(chuàng)建和管理多個復(fù)雜的子View赖草?
在許多剛?cè)腴T或者是初學(xué)者來說對于這種情況的處理方法就是addSubView
,需要多少個子視圖不停添加進(jìn)去就對了剪个。那么問題來了秧骑,
- 產(chǎn)生代碼量龐大而且邏輯復(fù)雜的ViewController,看著一個上千行代碼的ViewController是不是想死的心都有了扣囊?
- 產(chǎn)生的大量<nonatomic,strong>UIView占據(jù)的高內(nèi)存如何處理腿堤?
處理addSubView
這個方法,還有一種誤用就是:
[self.view addSubView:self.vc.view];
直接添加ViewController的view到當(dāng)前ViewController的view中如暖,這種方法倒是可以代碼的高耦合的問題笆檀,但是這種方法會產(chǎn)生一系列更加嚴(yán)重的問題:
- 直接add進(jìn)去的SubView不在ViewController的view hierarchy內(nèi),事件不會正常傳遞盒至,如:旋轉(zhuǎn)酗洒、觸摸等,屬于危險操作
- 違背CocoaTouch開發(fā)的設(shè)計MVC原則枷遂,ViewController應(yīng)該且只應(yīng)該管理一個view hierarchy
這也不行那也不可以樱衷,我們到底需要怎么來用呢?addChildViewController
才是我們需要的酒唉。
addChildViewController
是在iOS5之后出現(xiàn)的矩桂,在這之前人們一直都在忍受著上面我們講的種種陣痛。首先我們看看具體用法:
[self addChildViewController:newVC];
//[newVC willMoveToParentViewController:self];
[self.view addSubview:newVC.view];
[newVC didMoveToParentViewController:self];
[oldVC willMoveToParentViewController:nil];
[oldVC.view removeFromSuperview];
[oldVC removeFromParentViewController];
//[oldVC didMoveToParentViewController:nil];
上面代碼中寫出了添加和移除ChildViewController的具體寫法痪伦,添加過程:
- 通過
addChildViewController:
添加子控制器 - 這一步為隱式調(diào)用侄榴,系統(tǒng)在
addChildViewController:
方法后會自動調(diào)用方法willMoveToParentViewController:
- 將子控制器視圖添加進(jìn)主視圖
- 通知子控制器childViewController添加完成,這一步需要手動顯示調(diào)用
移除過程與之相反网沾,只是調(diào)用的幾個方法名不一樣癞蚕。
在Apple官方文檔上明確表示了必須要調(diào)用didMoveToParentViewController和willMoveToParentViewController方法來確認(rèn)完成過程執(zhí)行完畢,初學(xué)者需要特別注意和幾個方法的調(diào)用順序辉哥,使用不當(dāng)會導(dǎo)致UIViewControllerHierarchyInconsistency
的警告桦山。
介紹了使用方法攒射,在來介紹一下使用它的好處,好處就是規(guī)避了上面提到的兩種誤用產(chǎn)生的后果恒水,具體就是:
- 解決了代碼的高耦合
- 系統(tǒng)在收到內(nèi)存警告的時候會回收一些并未顯示的view会放,釋放內(nèi)存
- 這種方式add進(jìn)入的view是屬于當(dāng)前view hierarchy內(nèi),可以正常傳遞各種事件钉凌。
在使用addChildViewController:
還有一個比較高級的特性就是可以由自己選擇控制childViewController的Appearance callbacks鸦概。
//該方法返回NO則childViewController不會自動viewWillAppear和viewWillDisappear對應(yīng)的方法
- (BOOL)shouldAutomaticallyForwardAppearanceMethods
{
return NO;
}
//viewWillAppear調(diào)用設(shè)置為YES,viewWillDisappear調(diào)用設(shè)置為NO
[self.customChildViewController beginAppearanceTransition:YES animated:animated];
//對應(yīng)的DidAppear調(diào)用需要成對出現(xiàn)
[self.customChildViewController endAppearanceTransition];
這一篇關(guān)于UIViewController的介紹就寫到這里了就結(jié)束了甩骏。