大家好,又和大家見(jiàn)面了,之前做的幾個(gè)動(dòng)畫Demo的文章被好多人轉(zhuǎn)載和關(guān)注,感謝大家對(duì)我鼓勵(lì),說(shuō)實(shí)話,以前看別人的文章覺(jué)得很容易,自己堅(jiān)持寫文章才真正體會(huì)到不容易,加之最近工作中轉(zhuǎn)入Android開(kāi)發(fā),在iOS方面的貢獻(xiàn)速度急劇下降,但好歹,這篇水平不怎地的翻譯文章加一個(gè)小Demo做好了,實(shí)際上,我個(gè)人覺(jué)得,官方文檔遠(yuǎn)遠(yuǎn)比博客要實(shí)在的多(android的就沒(méi)那么幸運(yùn)了),但是受限于我們的時(shí)間和語(yǔ)言上的阻礙,大家往往都會(huì)忽略官方文檔,導(dǎo)致我們不能理解真正的官方倡導(dǎo)的開(kāi)發(fā)宗旨和意圖。所以,我們各位iOS開(kāi)發(fā)者,要多多研讀官方文檔啊,這是提高水平的最好機(jī)會(huì)!
本文是View Controller Programming Guide For iOS中的一個(gè)篇章,WWDC2015更新了這個(gè)guide,這篇也是最常用的一篇,所以將主要的東西翻譯一下,作為記錄。
Container View Controller 簡(jiǎn)稱容器類View Controller,在UIKit中,提供諸如UINavgationController、UITabBarController、UIPageViewController和UISplitViewController等,但有時(shí)候我們需要自己定義我們App中具有獨(dú)特交互邏輯的管理其他視圖控制器的容器類View Controller,比方說(shuō)側(cè)邊欄,比方說(shuō)ViewPager似的管理類試圖控制器,如何正確的構(gòu)建容器類的View Controller,保持被管理的View Controller的生命周期調(diào)度正確和平衡,是需要精細(xì)化設(shè)計(jì)的事情,而這篇就詳細(xì)交代了設(shè)計(jì)這樣的容器類ViewController的原則以及具體實(shí)現(xiàn)起來(lái)需要哪些相關(guān)的API,當(dāng)然,我也會(huì)加入實(shí)際工作中用到的一些Tips與結(jié)合Interface Builder來(lái)講述一些快速構(gòu)建容器類ViewController的辦法。
原文:
Implementing a Container View Controller
譯文:
和其他內(nèi)容視圖控制器一樣,容器視圖控制器也是管理一個(gè)根視圖View和一些其他內(nèi)容視圖View的視圖控制器,但是不同的是,容器類視圖控制器它的View是呈現(xiàn)被限制的嵌套在它的視圖層級(jí)中的其他視圖控制器的View,容器類視圖控制器決定了嵌入到它視圖中的其他視圖控制器的View的位置和大小,但是原有的View也可以管理它上面的內(nèi)容。
設(shè)計(jì)一個(gè)自定義的容器類視圖控制器
當(dāng)你設(shè)計(jì)容器類視圖控制器的時(shí)候,總是要理解容器(容器視圖控制器)與被包含的視圖控制器的關(guān)系,這種關(guān)系能幫助你清楚被管理的視圖控制器的內(nèi)容如何顯示到屏幕上以及本質(zhì)上是如何管理這些內(nèi)容的,在設(shè)計(jì)的流程中,需要考慮如下幾個(gè)問(wèn)題:
1.容器視圖控制器在呈現(xiàn)子視圖控制器的時(shí)候扮演什么角色?
2.有多少子視圖控制器能被同時(shí)呈現(xiàn)?(個(gè)人覺(jué)得在子視圖控制器切換的時(shí)候,需要著重考慮)
3.如果有的話,子視圖控制器之間是什么關(guān)系?(跟呈現(xiàn)邏輯相關(guān))
4.子視圖控制器如何增到容器視圖控制器中和如何從容器視圖控制器中移除?
5.子視圖控制器的View的尺寸和位置是否可以被改變?什么條件下會(huì)發(fā)生這些改變?
6.容器視圖控制器是否提供具體的組件或者類似于導(dǎo)航的東西來(lái)管理這些子視圖么?
7.子視圖控制器和容器視圖控制器的通訊方式是什么?以及容器視圖控制器是否需要傳送一些不是UIViewController提供的標(biāo)準(zhǔn)時(shí)間的特殊事件給子視圖控制器?
8.是否有不同的方式來(lái)呈現(xiàn)容器類視圖控制器,如果有,怎么呈現(xiàn)?
當(dāng)你思考完這些問(wèn)題并且制定出相應(yīng)的規(guī)則之后,實(shí)現(xiàn)一個(gè)容器類視圖控制器將是一件非常簡(jiǎn)單的事情。UIKit中唯一需要的條件是構(gòu)建容器視圖控制器和你要管理的視圖控制器之間的父子關(guān)系。這種父子關(guān)系能夠保證子視圖控制器能夠接收到系統(tǒng)發(fā)送的消息。(子視圖控制器的生命周期函數(shù)的正常調(diào)用等)除此之外,每一種容器類試圖控制器不同的地方就是在布局和管理期間對(duì)它包含的views的做的操作。你可以增加一些自定義的Views到一些定制的組件和導(dǎo)航的視圖層級(jí)中去。
例子:導(dǎo)航控制器
一個(gè)UINavigationController對(duì)象支持通過(guò)內(nèi)容的層級(jí)結(jié)構(gòu)來(lái)進(jìn)行導(dǎo)航。導(dǎo)航控制器在一個(gè)時(shí)間內(nèi)只能顯示一個(gè)子視圖的界面,上部的導(dǎo)航欄顯示當(dāng)前在子視圖控制器當(dāng)中的哪一個(gè),左邊按鈕是返回上一個(gè)層級(jí),導(dǎo)航到下一層級(jí)新的子視圖控制器會(huì)往左滑入并且可以包含一些按鈕或者是可用的表格。
導(dǎo)航在子視圖控制器之間是共享的,當(dāng)一個(gè)視圖的界面上有一些按鈕或者是表格,關(guān)聯(lián)上子視圖控制器,子視圖控制器可以要求導(dǎo)航控制器push到一個(gè)新的視圖控制器到容器View上,子視圖控制器更新內(nèi)容,但是導(dǎo)航控制器管理轉(zhuǎn)場(chǎng)的動(dòng)畫。導(dǎo)航控制器也管理著可以使當(dāng)前視圖控制器消失的導(dǎo)航欄。
圖5-1顯示了一個(gè)導(dǎo)航控制器和它上面視圖的結(jié)構(gòu),大部分區(qū)域是被子視圖控制器給填充的,只有一小部分區(qū)域是導(dǎo)航欄。
圖 5-1Structure of a navigation interface
無(wú)論是寬松還是緊湊的環(huán)境下,一個(gè)導(dǎo)航控制器僅僅能顯示一個(gè)子視圖控制器。導(dǎo)航控制器可以重新布局子視圖控制器以重新適應(yīng)可用空間。
例子:分欄控制器
一個(gè)UISplitViewController對(duì)象可以分為主副區(qū)域來(lái)顯示兩個(gè)子視圖控制器。在這種安排下,一個(gè)視圖控制器的內(nèi)容(主)決定另外一個(gè)副區(qū)域顯示哪個(gè)子視圖控制器。兩個(gè)視圖控制器的顯示是可以配置的,同時(shí)也取決于兩個(gè)子視圖控制器的的當(dāng)前環(huán)境。在寬松的水平環(huán)境下,分欄控制器可以顯示兩個(gè)子視圖控制器,在緊湊的模式下,分欄控制器只能在同一時(shí)間顯示一個(gè)試圖控制器。
圖5-2展示了一個(gè)分欄控制器在寬松的水平環(huán)境下的結(jié)構(gòu),分欄控制器僅僅包含自己默認(rèn)的容器view,在這個(gè)例子中,兩個(gè)子視圖控制器顯示在兩邊,子視圖控制器的大小是可配置的,從而可以顯示主控制器。
圖5-2A split view interface
在interface builder中配置一個(gè)容器類視圖控制器
在設(shè)計(jì)階段創(chuàng)建一個(gè)父子視圖容器類控制器,需要在storyboard中增加一個(gè) container view 對(duì)象,如圖5-3。container view對(duì)象是一個(gè)占位符,用來(lái)顯示子視圖控制器的內(nèi)容。使用視圖的大小和位置確定子視圖控制器的根視圖在容器類視圖控制器中的位置和大小。
5-3Adding a container view in Interface Builder
當(dāng)你使用一個(gè)或者多個(gè)container view 來(lái)加載view controller 的時(shí)候,interface builder 也會(huì)將子視圖控制器的view與container view連接并加載。子視圖控制器在此期間必須完成初始化并且父子關(guān)系也會(huì)被創(chuàng)建。
如果你不使用interface builder創(chuàng)建父子關(guān)系,你必須通過(guò)代碼的形式構(gòu)建,詳情見(jiàn)后面的“增加一個(gè)子視圖控制器到你的容器里”
實(shí)現(xiàn)一個(gè)自定義的容器類試圖控制器
為了實(shí)現(xiàn)容易類視圖控制器,你必須建立容器類視圖控制器與子視圖控制器的父子關(guān)系。在你即將要管理這些子視圖控制器的view的之前必須構(gòu)建好這些父子關(guān)系。這樣可以讓UIKit知道你正在管理的子視圖控制器的視圖的位置和大小,你可以通過(guò)interface builder或者代碼進(jìn)行構(gòu)建這些關(guān)系。當(dāng)你使用代碼創(chuàng)建這些父子關(guān)系的時(shí)候,你可以準(zhǔn)確的增加或者移除子視圖控制器。
增加一個(gè)子視圖控制器到你的容器里
為了能夠通過(guò)代碼將子視圖控制器添加到容器類視圖控制器中,如下步驟可以創(chuàng)建父子關(guān)系:
1.調(diào)用容器視圖控制器 addChildViewController: 的方法,這個(gè)方法用來(lái)告訴UIKit你的容器類視圖控制器即將要管理一個(gè)子視圖控制器。
2.增加子視圖控制器的view到 容器視圖控制的view(或者Container View)的視圖層級(jí)結(jié)構(gòu)中,千萬(wàn)別忘記設(shè)置大小的位置。
3.為管理子視圖控制的view的大小和位置添加約束條件。
4.調(diào)用子視圖控制的 didMoveToParentViewController: 方法。
表5-1展示了一個(gè)容器類視圖控制器怎樣嵌入子視圖控制器,當(dāng)創(chuàng)建完父子關(guān)系之后,在將子視圖加入到容器視圖中,設(shè)置了子視圖的frame 、size。設(shè)置frame與size是非常重要的也是保證正確的將子視圖顯示在容器中,在增加完之后,容器類視圖控制器調(diào)用didMoveToParentViewController:方法,這是因?yàn)橐o子視圖控制器能夠響應(yīng)變化的機(jī)會(huì)。(調(diào)用相應(yīng)的生命周期函數(shù)布局根視圖)
- (void) displayContentController: (UIViewController*) content { [self addChildViewController:content]; content.view.frame = [self frameForContentController]; [self.view addSubview:self.currentClientView]; [content didMoveToParentViewController:self]; }
表 5-1增加一個(gè)子視圖控制器到容器中
在上一個(gè)例子中,你會(huì)發(fā)現(xiàn)你僅僅調(diào)用子視圖控制器的didMoveToParentViewController:,這是因?yàn)槿萜黝愐晥D控制器調(diào)用addChildViewController:會(huì)引發(fā)自動(dòng)調(diào)用子視圖控制器的willMoveToParentViewController:,你必須自己調(diào)用容器類視圖控制器的didMoveToParentViewController:方法的原因是你必須在完成將子視圖控制器的view嵌入到容器類視圖控制器的視圖層級(jí)之后才能調(diào)用該方法。
當(dāng)你使用AutoLayout的時(shí)候,請(qǐng)?jiān)趯⑴渲眉s束條件的工作放在子視圖控制的view已經(jīng)嵌入到容器類視圖控制器的層級(jí)結(jié)構(gòu)中。你的約束條件必須僅僅能影響子視圖控制器的根視圖的位置和大小,不要修改根視圖的內(nèi)容或者是其他子視圖控制器視圖結(jié)構(gòu)中的視圖。
移除一個(gè)子視圖控制器
為了移除一個(gè)子視圖控制器,移除父子關(guān)系必須遵循以下步驟:
1.調(diào)用子視圖的 willMoveToParentViewController: 方法 參數(shù)為nil
2.移除任何你對(duì)子視圖控制的view的約束條件
3.將子視圖控制的view從容器類視圖控制器的層級(jí)結(jié)構(gòu)中移除
4.調(diào)用子視圖控制器的 removeFromParentViewController 方法 從而最終結(jié)束父子關(guān)系
永久性的斷絕子視圖控制器與容器視圖控制器的父子關(guān)系,移除一個(gè)子視圖控制器必須保證你將不再需要引用這個(gè)子視圖控制器。舉個(gè)例子,一個(gè)導(dǎo)航控制器在push進(jìn)來(lái)一個(gè)新的子視圖控制器到導(dǎo)航棧中并不需要移除當(dāng)前的子視圖控制器,僅僅在該子視圖控制器從導(dǎo)航棧中彈出才進(jìn)行移除子視圖控制器。
表5-2展示了一個(gè)子視圖控制器如何從容器中移除.調(diào)用willMoveToParentViewController:的方法,參數(shù)為nil,給子視圖控制器一個(gè)應(yīng)對(duì)變化的一個(gè)機(jī)會(huì)。removeFromParentViewController也會(huì)調(diào)用子視圖控制器的didMoveToParentViewController: 的方法,傳遞nil的參數(shù)。最終解除與容器的父子關(guān)系。
- (void) hideContentController: (UIViewController*) content { [content willMoveToParentViewController:nil]; [content.view removeFromSuperview]; [content removeFromParentViewController]; }
表 5-2 從容器中移除子視圖控制器
子視圖控制器之間的轉(zhuǎn)場(chǎng)
當(dāng)你想要使用包含移入移出動(dòng)畫來(lái)切換子視圖控制器的時(shí)候,在這些動(dòng)畫之前,你需要保證兩個(gè)子視圖控制器都是容器視圖控制器的內(nèi)容的一部分,并且你要使當(dāng)前子視圖控制器知道它即將要開(kāi)始動(dòng)畫。在動(dòng)畫期間,要使得新的子視圖控制器進(jìn)入到預(yù)定位置,并且要移出舊的子視圖控制器的view。在完成動(dòng)畫的時(shí)候,需要徹底完成移出子視圖控制器的工作。
表5-3展示了一個(gè)使用轉(zhuǎn)場(chǎng)動(dòng)畫完成一個(gè)子視圖控制器切換到另外一個(gè)子視圖控制器的例子。在這個(gè)例子中,新的視圖控制器開(kāi)啟動(dòng)畫移動(dòng)到當(dāng)前正在進(jìn)行移出屏幕的視圖控制器的矩形位置上。當(dāng)動(dòng)畫完成,回調(diào)的block會(huì)從容器視圖控制器移出子視圖控制器,在這個(gè)例子中,transitionFromViewController:toViewController:duration:options:animations:completion: 這個(gè)方法自動(dòng)的更新容器視圖控制器中的view層級(jí)結(jié)構(gòu),所以你不需要增加或者刪除views。
- (void)cycleFromViewController: (UIViewController*) oldVC toViewController: (UIViewController*) newVC { // Prepare the two view controllers for the change. [oldVC willMoveToParentViewController:nil]; [self addChildViewController:newVC];
// Get the start frame of the new view controller and the end frame // for the old view controller. Both rectangles are offscreen. newVC.view.frame = [self newViewStartFrame]; CGRect endFrame = [self oldViewEndFrame];
// Queue up the transition animation. [self transitionFromViewController: oldVC toViewController: newVC duration: 0.25 options:0 animations:^{ // Animate the views to their final positions. newVC.view.frame = oldVC.view.frame; oldVC.view.frame = endFrame; } completion:^(BOOL finished) { // Remove the old view controller and send the final // notification to the new view controller. [oldVC removeFromParentViewController]; [newVC didMoveToParentViewController:self]; }]; }
管理子視圖控制器的外觀更新
在將子視圖控制器增加到容器視圖控制器之后,容器類視圖控制器自動(dòng)的將外觀相關(guān)的消息傳送給子視圖。這是正常的情況,因?yàn)楸WC了所有的事件都被發(fā)送。然而,有些時(shí)候,默認(rèn)的行為在一條指令中發(fā)送一些事件,然而并沒(méi)有使容器察覺(jué)。例如,如果多個(gè)子視圖控制器同時(shí)更新它們view的狀態(tài),你可能會(huì)想合并這些變化,因此外觀的回調(diào)全部發(fā)生在同一時(shí)間。
為了接管處理這些外觀變化的回調(diào),需要在你的容器類視圖控制器中重寫shouldAutomaticallyForwardAppearanceMethods 的方法,返回值為NO,在表5-4中的例子,返回No使得UIKit知道你的容器類視圖控制器會(huì)通知它的子視圖控制器在其界面中的變化。
- (BOOL) shouldAutomaticallyForwardAppearanceMethods { return NO; }
5-4表 automatic appearance forwarding
當(dāng)顯示的轉(zhuǎn)場(chǎng)發(fā)生的時(shí)候,調(diào)用子視圖的beginAppearanceTransition:animated:與endAppearanceTransition 來(lái)顯現(xiàn)。例如,你的容器有一個(gè)單一的子視圖控制器,你可以在不同的生命周期函數(shù)中調(diào)用,確保子視圖控制器獲取到這些消息。如表5-5。
-(void) viewWillAppear:(BOOL)animated { [self.child beginAppearanceTransition: YES animated: animated]; } -(void) viewDidAppear:(BOOL)animated { [self.child endAppearanceTransition]; } -(void) viewWillDisappear:(BOOL)animated { [self.child beginAppearanceTransition: NO animated: animated]; } -(void) viewDidDisappear:(BOOL)animated { [self.child endAppearanceTransition]; }
表 5-5
創(chuàng)建一個(gè)容器類視圖控制器的建議
設(shè)計(jì),開(kāi)發(fā)和測(cè)試一個(gè)新的容器類視圖控制器是需要花費(fèi)時(shí)間的,即使獨(dú)立的行為是簡(jiǎn)單易懂的,但將視圖控制器作為整體仍然是復(fù)雜的,當(dāng)實(shí)現(xiàn)一個(gè)容器類控制器的類的時(shí)候,請(qǐng)考慮以下tips:
僅僅有獲取子視圖控制器根視圖的權(quán)限. 容器視圖控制器僅僅只需要有每一個(gè)子視圖控制器根視圖的訪問(wèn)權(quán)限比如返回子視圖的 view的屬性。從不應(yīng)該獲取其他views的權(quán)限
**子視圖控制器應(yīng)該最小限度的了解它的容器(也就是說(shuō)子視圖控制器不需要做很多的配置才能夠被添加到容器中,子視圖控制器與容器控制器應(yīng)該是松耦合的) **.子視圖控制器應(yīng)該把精力放在自己內(nèi)容的管理上. 如果一個(gè)容器類視圖控制器想要影響子視圖控制器上的內(nèi)容,不要直接操作,使用代理設(shè)計(jì)模式去管理這些影響
Design your container using regular views first. Using regular views (instead of the views from child view controllers) gives you an opportunity to test layout constraints and animated transitions in a simplified environment. When the regular views work as expected, swap them out for the views of your child view controllers.(這段翻譯不知道怎么翻,還是看原文比較舒服)
委托控制子視圖控制器
一個(gè)容器試圖控制器可以代理自身的appearance和一個(gè)或者多個(gè)子視圖控制器,可以采用如下方式代理:
讓子視圖控制器決定狀態(tài)欄風(fēng)格. 將子視圖控制器作為修改狀態(tài)欄的代理, 重寫 childViewControllerForStatusBarStyle 和 childViewControllerForStatusBarHidden 在容器視圖控制器中的兩個(gè)方法
讓子視圖控制器提供期許的尺寸大小,容器類控制器有靈活的適應(yīng)性的布局,能夠使用子視圖控制器的preferredContentSize 來(lái)幫助定位其尺寸
例子(Demo持續(xù)更新)
以下demo是做的一個(gè)小小的例子,時(shí)間倉(cāng)促,沒(méi)有過(guò)多講解,后期會(huì)不斷豐富這個(gè)例子,從而實(shí)現(xiàn)復(fù)雜的容器類視圖控制器,并講解如何自定義容器視圖控制器的轉(zhuǎn)場(chǎng)動(dòng)畫(不可交互與可交互)
demo是使用XCode7 beat6開(kāi)發(fā)的,swift2.0,這點(diǎn)大家周知
2015.9.9 ContainerViewController