規範之力:正確使用UIViewController(1)

VC的設計初衷==》一個ViewController應該且只應該管理一個view hierarchy;
目錄

一.UIViewController編輯本段回目錄

做iOS開發的經常會和UIViewController打交道,從類名可知UIViewController屬於MVC模型中的C(Controller),說的更具體點它是一個視圖控制器,管理着一個視圖(view)。
UIViewController的view是lazy loading的,當你訪問其view屬性的時候,view會從xib文件載入或者通過代碼創建(覆蓋loadView方法,自定義其view hierarchy),並返回,如果要判斷一個View Controller的view是否已經被加載需要通過其提供的isViewLoaded方法來判斷。 view加載後viewDidLoad會被調用,這裏可以進行一些數據的請求或加載,用來更新你的界面。 當view將被加入view hierarchy中的時候viewWillAppear會被調用,view完成加入的時候viewDidAppear會被調用,同樣當view將要從view hierarchy中移除的時候viewWillDisappear會被調用,完成移除的時候viewDidDisappear會被調用。  當內存緊張的時候,所有的UIViewController對象的didReceiveMemoryWarning會被調用,其默認實現是 如果當前viewController的view的superview是nil的話,則將view釋放且viewDidUnload會被調用,viewDidUnload中你可以進行後繼的內存清理工作(主要是界面元素的釋放,當再次加載的時候需要重建)。
如果想要展示一個View Controller,一般有如下一種途徑
  1. 設置成Window的rootViewController(iOS 4.0之前UIWindow並沒有rootViewController屬性,只能通過addSubview的方式添加一個View Controller的view)
  2. 使用某個已經存在的Container來展示,比如使用UINavigationController來展示某個View Controller [navigationController pushViewController:vc animated:YES];
  3. 以模態界面的方式展現出來 presentModalViewController
  4. 以addSubview的方式將使其view作爲另一個View Controller的view的subView

直接使用4種方法是比較危險的,上一級 View Controller並不能對當前View Controller的 生命週期相關的函數進行調用,以及旋轉事件的傳遞等。

二.Hierarchy編輯本段回目錄

我們知道一個View可以將另一個View添加爲子View(subview),構成一個View Hierarchy.當某一個View添加到window的View Hierarchy中時,將被“顯示”。每一個View Controller管理着的其實就是一個View Hierarchy.而View Controller本身可以有Child View Controller,(需要考量此話。vc中個人認爲只存在前後關係,不存在父子層級關係)所以也存在一個 View Controller Hierarchy的概念,當View Controller收到上層傳來的諸如旋轉,顯示事件的時候,需要傳遞給它的Child View Controller. 一般情況下,View Hierarchy 和 View Controller Hierarchy需要保持一致性,比如一個View Controller的view的superView是由其parent view controller管理着

三.Container編輯本段回目錄

一個iOS的app很少只由一個ViewController組成,除非這個app極其簡單。 當有多個View Controller的時候,我們就需要對這些View Controller進行管理。 那些負責一個或者多個View Controller的展示並對其視圖生命週期進行管理的對象,稱之爲容器,大部分容器本身也是一個View Controller,這樣的容器可以稱之爲Container View Controller,也有極少數容器不是View Controller,比如UIPopoverController,其繼承於NSObject。
我們常用的容器有 UINavigationController,UITabbarController等,一般容器有一些共同的特徵:

  1. 提供對Child View Controller進行管理的接口,比如添加Child View Controller,切換Child View Controller的顯示,移除Child View Controller 等
  2. 容器“擁有”所有的Child View Controller
  3. 容器需要負責 Child View Controller的appearance callback的調用(viewWillAppear,viewDidAppear,viewWillDisaapper,viewDidDisappear),以及旋轉事件的傳遞
  4. 保證view hierarchy 和 view controller hierarchy 層級關係一致,通過parent view controller將child view controller和容器進行關聯


從上面可以看出來,實現一個Container View Controller並不是一個簡單的事情,好在iPhone的界面大小有限,一般情況下一個View Controller的view都是充滿界面或者系統自帶容器的,我們無需自己創建額外的容器,但是在iPad中情況就不同了。

四.Custom Container View Controller編輯本段回目錄

在iOS 5之前框架並不支持自定義 Container View Controller, iOS 5開始開放了一些新的接口來支持支持自定義容器
 addChildViewController:
removeFromParentViewController
transitionFromViewController:toViewController:duration:options:animations:completion:
willMoveToParentViewController:
didMoveToParentViewController:
其中前兩個接口比較重要,可以直接改變View Controller 的 Hierarchy。
有點意外的是,在不做任何額外設置的情況下進行如下操作
[viewController.viewaddSubview:otherViewController.view]
iOS 5中otherViewController是可以立刻收到viewWillAppear和viewDidAppear的調用。

至於旋轉事件的傳遞以及其他時機viewWillAppear viewDidAppear的調用是需要建立在 [viewController addChildViewController:otherViewController]基礎上的。
當我們需要在iOS 4上實現自定義容器,或者有時候我們不想讓viewWillAppear這類方法被自動調用,而是想自己來控制,這個時候我們就得需要手動來調用這些方法,而不是由框架去自動調用。(謹慎,實質上這是不規範的做法。這也是本題所言“規範之力”如何作爲之)
 iOS 5中可以很方便的禁用掉自動調用的特性,覆蓋automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers返回NO
但是單單覆蓋這個方法在iOS5下還是有問題的,當執行下面的語句的時候
[viewController.viewaddSubview:otherViewController.view]
otherViewController還是是可以立刻收到viewWillAppear和viewDidAppear的調用。
 解決這一問題的方法就是在iOS5的時候調用[viewController.view addSubview:otherViewController.view]之前 進行如下操作
[viewControlleraddChildViewController:otherViewController]  
(除非透徹理解了規則,不侷限於規則之力的範疇。超出規則而使用。要不輕易別這麼幹)

總的來說實現兼容iOS 4和iOS 5的容器有不少問題和注意點的:

  1. view加入view層級前後分別調用viewWillAppear和viewDidAppear;容器的viewWillAppear,viewDidAppear,viewWillDisappear,viewDidDisappear中需要對當前顯示的Child View Controller調用相同的方法,容器需要保證Child View Controller的viewWillAppear調用之前Child View Controller的view已經load了.還有一點就是保證容器的View不會出現bounds爲CGRectZero的情況,因爲如果此View包含多個subview,其bounds改變的時候subview會根據自己的autoresizingMask改變frame,但是當bounds變爲0再變爲非0的時候,subview的frame就有可能不是你想要的了(比如某個subview的autoresizingMask爲UIViewAutoresizingFlexibleBottomMargin)
  2. 容器的shouldAutorotateToInterfaceOrientation中需要檢測每一個Child View Controller的shouldAutorotateToInterfaceOrientation如果一個不支持,則看做不支持
  3. 容器的willRotateToInterfaceOrientation,didRotateFromInterfaceOrientation,willAnimateRotationToInterfaceOrientation方法中需要將這些事件傳遞給所有的Child View Controller
  4. 由於UIViewController的parentViewController屬性爲只讀,且iOS4中沒有提供容器支持的接口(iOS 5中容器支持的接口會間接的維護這個屬性),所以爲了使得childViewController和容器得以關聯,我們可以頂一個View Controller的基類,添加一個比如叫做superController的屬性用來指定對應的parentViewController
  5. 由於UIViewController的interfaceOrientation爲只讀屬性,且iOS5中沒有提供容器接口,所以UIViewController的這個interfaceOrientation變的不可信,爲了取得當前UIViewController的orientation我們可以用UIWindow下的rootViewController的interfaceOrientation的值
  6. 容器的viewDidUnload方法中需要對view未釋放的childViewController的view進行釋放,且調用其viewDidUnload方法
蘋果對UIViewController以及其使用有着非常詳細的文檔 UIViewController Reference , ViewController Programming Guide。


源:http://geeklu.com/2012/05/custom-container-view-controller/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章