關於自定義檢視容器(Container View Controller)

chenyxh2005發表於2014-12-07

蘋果對UIViewController以及其使用有著非常詳細的文件 UIViewController Reference , ViewController Programming Guide 。

一.UIViewController

作為iOS開發, 經常會和UIViewController打交道,從類名便可知道UIViewController屬於MVC模型中的C(Controller),說的更具體點它是一個檢視控制器,管理著一個檢視(view)。

UIViewController的view是lazy loading的,當你訪問其view屬性的時候,它會從xib檔案載入或者通過程式碼建立(覆蓋loadView方法,自定義其view hierarchy),並返回view物件,如果要判斷一個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. 設定成UIWindow的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的 生命週期相關的函式進行呼叫,以及旋轉事件的傳遞等。

二.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都是充滿介面或者系統自帶容器的,我們無需自己建立額外的容器。

在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:

有點意外的是,在不做任何額外設定的情況下進行如下操作

[viewController.view addSubview:otherViewController.view]

iOS 5中otherViewController是可以立刻收到viewWillAppear和viewDidAppear的呼叫。

至於旋轉事件的傳遞以及其他時機viewWillAppear viewDidAppear的呼叫是需要建立在 [viewController addChildViewController:otherViewController]基礎上的。

當我們需要在iOS 4上實現自定義容器,或者有時候我們不想讓viewWillAppear這類方法被自動呼叫,而是想自己來控制,這個時候我們就得需要手動來呼叫這些方法,而不是由框架去自動呼叫。 iOS 5中可以很方便的禁用掉自動呼叫的特性,覆蓋automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers返回NO

但是單單覆蓋這個方法在iOS5下還是有問題的,

[viewController.view addSubview:otherViewController.view]

otherViewController還是是可以立刻收到viewWillAppear和viewDidAppear的呼叫。 
解決這一問題的方法就是在iOS5的時候呼叫[viewController.view addSubview:otherViewController.view]之前 進行如下操作

[viewController addChildViewController:otherViewController]

實現相容iOS 4和iOS 5的容器還是有不少問題和注意點的

  1. view加入view層級前後分別呼叫viewWillAppear和viewDidAppear;容器的viewWillAppear,viewDidAppear,viewWillDisappear,viewDidDisappear中需要對當前顯示的Child View Controller呼叫相同的方法
  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方法

相關文章