視圖控制器管理著構成應用程序用戶界面中的一部分視圖,其負責加載和處理這些視圖,管理與這些視圖的交互,并協調視圖對其展示的數據內容的變更作出響應。視圖控制器還能與其他視圖控制器對象協調工作,幫助管理應用程序的整體界面。
視圖控制器的主要職責包括以下內容:
- 更新視圖的內容來響應底層數據的變化。
- 響應用戶與視圖的交互。
- 調整視圖尺寸并管理整個界面的布局。
視圖控制器的作用
視圖控制器是應用程序內部結構的基礎。每個應用程序至少含有一個視圖控制器,大多數應用程序都含有多個視圖視圖控制器。每個視圖控制器管理著應用程序的用戶界面,以及該界面和底層數據之間的交互。視圖控制器也便于在不同用戶界面之間的轉換。
UIViewController
類定義了一些方法和屬性來管理視圖、處理事件、從一個視圖控制器切換到另一個視圖控制器和協調應用程序的其他部分,可以通過繼承UIViewController
(或其子類之一)來添加實現應用程序行為所需的自定義代碼。
有兩種類型的視圖控制器:
- 內容視圖控制器,其管理著應用程序內容的一部分。
- 容器視圖控制器,其收集來自于其他視圖控制器的信息并以便于導航的方式呈現或者以不同方式呈現這些視圖控制器的內容。
視圖管理
視圖控制器最重要的作用是管理視圖的層次結構。每個視圖控制器都含有一個包含所有視圖控制器內容的根視圖,可以添加需要顯示內容的視圖到該根視圖中。下圖顯示了視圖控制器和視圖之間的內置關系,視圖控制器總是具有對其根視圖的強引用,并且每個視圖都具有對其子視圖的強引用。
內容視圖控制器自己管理其包含的所有視圖,容器視圖控制器管理其所包含的所有視圖以及來自其一個或多個子視圖控制器的根視圖。容器視圖控制器并不管理其子視圖控制器的內容,只管理其子視圖控制器的根視圖。下圖說明了UISplitViewController
與其子視圖控制器之間的關系。UISplitViewController
管理其子視圖的整體尺寸和位置,但子視圖控制器管理這些視圖的實際內容。
數據封送
視圖控制器充當其管理的視圖與應用程序數據之間的媒介。子類化UIViewController
的時候,可以添加任何需要在子類中管理的數據變量。添加自定義變量會創建一個如下圖所示的關系,其中視圖控制器具有對數據的引用以及用于呈現該數據的視圖。
應該始終在視圖控制器和數據對象中保持清晰的職責分離。大多數確保數據結構完整性的邏輯應屬于數據對象本身。視圖控制器可以驗證來自視圖的輸入,然后以數據對象需要的格式打包輸入,但是應該最小化視圖控制器在管理實際數據中的角色。
UIDocument
對象是一種獨立于視圖控制器管理數據的方式。文檔對象是一個知道如何讀寫數據到持久存儲的控制器對象。子類化UIDocument
時,可以添加任何需要的邏輯和方法來提取數據,并將其傳遞給視圖控制器或者應用程序的某部分。視圖控制器可以存儲其接收到的任何數據的副本,以便更新視圖,但文檔仍然擁有真實的數據。
用戶交互
視圖控制器是響應者對象,能夠處理響應者鏈中傳遞的事件,但視圖控制器很少直接處理觸摸事件。相反,視圖通常會處理自己的觸摸事件,并將結果報告給其關聯的委托或目標對象(通常是視圖控制器)的方法。因此,視圖控制器中的大多數事件都是使用委托方法或操作方法處理的。
資源管理
視圖控制器對其視圖和它創建的任何對象承擔全部責任。UIViewController
類自動處理視圖管理的大多數方面,例如,UIKit自動釋放不再需要的任何視圖相關的資源。子類化UIViewController
時,需要自己負責管理創建的任何對象。
當可用空閑內存不足時,UIKit會要求應用程序釋放不再需要的資源。其中一種方式是通過調用視圖控制器的didReceiveMemoryWarning
方法來刪除對不再需要的對象的引用或者稍后重新創建。例如,在該方法中刪除緩存的數據。發生內存不足的情況時,釋放盡可能多的內存非常重要。消耗太多內存的應用程序可能會被系統徹底終止以恢復內存。
適應性
視圖控制器負責呈現其視圖,并使該呈現適應底層環境。每個iOS應用程序都應該能夠在iPad上運行,并且可以在幾種不同尺寸的iPhone上運行。不是為每個設備提供不同的視圖控制器和視圖層,而是使用單個視圖控制器來更簡單地調整其視圖以適應不斷變化的空間需求。
在iOS中,視圖控制器需要處理粗粒度的變化和細粒度的變化。當視圖控制器的特性改變時,會發生粗粒度的變化。特征是描述整體環境的屬性,例如顯示比例。其中兩個最重要的特性是視圖控制器的水平和垂直尺寸類別,是它們表示視圖控制器在給定維度中有多少空間。可以使用size class changes來改變布局視圖的方式,如下圖所示。如果horizontal size class是規則的,視圖控制器利用額外的水平空間來排列其內容。如果horizontal size class是緊湊的,視圖控制器垂直排列其內容。
根據給定的size class,可以隨時進行更細粒度的尺寸更改。當用戶將iPhone從縱向旋轉到橫向時,size class可能不會改變,但屏幕尺寸通常會改變。在使用自動布局時,UIKt會自動調整視圖的尺寸和位置以匹配新尺寸。視圖控制器可以根據需要進行其他調整。
視圖控制器層次結構
應用程序的視圖控制器之間的關系定義了每個視圖控制器所需的行為,維護正確的視圖控制器關系可以確保自動行為在需要時傳遞給正確的視圖控制器。如果違反了UIKit規定的規則和呈現關系,則應用程序的表現可能和預期不一致。
根視圖控制器
根視圖控制器是視圖控制器層次結構的錨點。每個窗口只有一個根視圖控制器,此根視圖控制器的內容填充該窗口。根視圖控制器定義了用戶看到的初始內容。下圖顯示了根視圖控制器和窗口之間的關系,因為窗口本身沒有可見的內容,所以視圖控制器的視圖提供了所有的內容。
根視圖控制器可以從UIWindow
對象的rootViewController
屬性訪問。當使用storyboard來配置視圖控制器時,UIKit會在啟動時自動設置該屬性的值。對于以編程方式創建的窗口,必須自己設置根視圖控制器。
容器視圖控制器
容器視圖控制器允許使用更易于管理和可重用的界面來組裝復雜的界面。容器視圖控制器將一個或多個子視圖控制器的內容與可選的自定義視圖混合在一起,來創建其最終界面。例如,UINavigationController
對象顯示來自其子視圖控制器的內容以及由其管理的導航欄和可選工具欄。UIKit包含多個容器視圖控制器,包括UINavigationController
、UISplitViewController
和UIPageViewController
。
容器視圖控制器的視圖總是會填充給定的空間,其通常被指定為窗口的根視圖控制器。容器視圖控制器也可以以模態的方式呈現,或者作為其他容器的子項安裝。下圖顯示了在容器并排放置兩個子視圖。
由于容器視圖控制器管理其子項,UIKit定義了如何在自定義容器中設置這些子項的規則。
呈現一個視圖控制器
呈現一個視圖控制器時,通常會隱藏當前視圖控制器的內容來將當前視圖控制器的內容替換為新視圖控制器的內容。呈現最常用于模態地顯示新內容。在呈現一個視圖控制器時,UIKit會在呈現視圖控制器和其呈現的視圖控制器之間創建如下圖所示的關系。
當呈現涉及到容器視圖控制器時,UIKit可能會修改呈現鏈來簡化必須編寫的代碼。不同的呈現風格對應的視圖在屏幕上的顯示方式有不同的規則,例如全屏呈現總是覆蓋整個屏幕。在呈現一個視圖控制器時,UIKit會查找為呈現提供合適上下文的視圖控制器。在許多情況下,UIKit會選擇最近的容器視圖控制器,但也可能選擇窗口的根視圖控制器。在某些情況下,也可以直接告訴UIKit哪個視圖控制器定義了呈現上下文,并且應該處理呈現。
下圖顯示了容器視圖控制器為呈現提供上下文的原因。在執行全屏呈現時,新視圖控制器需要覆蓋整個屏幕。容器視圖控制器決定是否處理呈現,而不需要其子視圖控制器知道容器視圖的邊界。
設計技巧
視圖控制器是在iOS上運行的應用程序的基本工具,并且UIKit的視圖控制器基礎結構可以很容易地創建復雜的界面,而無需編寫大量的代碼。在實現我們自己的視圖控制器時,請使用一下提示和指導原則,以確保我們不會干涉可能會干擾系統預期的自然行為。
盡可能使用系統提供的視圖控制器
許多iOS框架定義了視圖控制器,使用這些系統提供的視圖控制器可一節省時間并確保為用戶提供一致的體驗。大多數系統控制器都是為特定任務而設計的某些控制器提供對用戶數據(如聯系人)的訪問,也可能提供訪問硬件或提供專門調整的界面來管理媒體。例如,UIKit中的UIImagePickerController
類顯示用于獲取圖片和視頻以及訪問用戶相機膠卷的標準界面。
在創建自己的自定義視圖控制器前,請查看現有的框架是否存在能夠執行需要的任務的視圖控制器:
- UIKit框架提供視圖控制器用于顯示彈窗警告,拍照和錄像,以及管理iCloud上的文件。UIKit還定義許多可用于組織內容的標準容器視圖控制器。
- GameKit框架提供了視圖控制器可用于匹配玩家以及管理排行榜、成就和其他游戲功能。
- Address Book UI框架提供了用于顯示和選擇聯系人信息的視圖控制器。
- MediaPlayer框架提供了用于播放和管理視頻以及從用戶庫中選擇媒體資源的視圖控制器。
- EventKit UI框架提供了用于顯示和編輯用戶日歷數據的視圖控制器。
- GLKit框架提供了用于管理OpenGL渲染界面的視圖控制器。
- Multipeer Connectivity框架提供了用于檢測其他用戶并邀請他們進行連接的視圖控制器。
- Message UI框架提供了用于撰寫電子郵件和SMS消息的視圖控制器。
- PassKit框架提供了用于顯示通行證并將其添加到Passbook的視圖控制器。
- Social框架為Twitter、Facebook和其他社交媒體網站提供了編輯消息的視圖控制器。
- AVFoundation框架提供了用于顯示媒體資源的視圖控制器。
重要:切勿修改系統提供的視圖控制器的視圖層次結構。每個視圖控制器都擁有其視圖層次結構,并負責維護該層次結構的完整性。進行更改可能會在代碼中引入錯誤,或阻止其所屬視圖控制器的正常運行。使用系統視圖控制器時,總是依靠視圖控制器公開的可用方法和屬性進行所有修改的。
有關使用特定視圖控制器的信息,可查看相應框架的參考文檔。
保證每個視圖控制器獨立運行
視圖控制器應始終是自給自足的對象,沒有視圖控制器應該含有有關于另一個視圖控制器的內部工作或視圖層次結構的信息。在兩個視圖控制器需要來回通信或傳遞數據的情況下,它們應該始終使用明確定義的公共接口來實現。
代理設計模式經常用于管理視圖控制器之間的通信。通過委托,一個對象定義了一個相關的委托對象進行通信的協議,該委托對象是任何符合該協議的對象。委托對象的確切類型是不重要的,重要的是它實現了協議的方法。
僅將根視圖用作其他視圖的容器
僅將視圖控制器的根視圖用作其余內容的容器。使用根視圖作為容器可以為所有視圖提供一個共同的父視圖,這使得許多布局操作變得更簡單。許多自動布局約束需要共同的父視圖來正確布置視圖。
知道數據該存在于哪里
在模型 - 視圖 - 控制器設計模式中,視圖控制器的作用是促進模型對象和視圖對象之間的數據移動。視圖控制器可能會將一些數據存儲在臨時變量中并執行一些驗證,但其主要職責是確保其視圖包含準確的信息。而模型數據對象負責管理實際數據并確保數據的完整性。
UIDocument
和UIViewController
類之間的關系就是一個數據和接口分離的例子。具體而言,兩者之間不存在默認關系。UIDocument
對象負責協調數據的加載和保存,而UIViewController
對象協調屏幕上的視圖顯示。如果需要在兩個對象之間創建關系,請記住視圖控制器應該只緩存文檔中的信息以提高效率,實際的數據仍然屬于文檔對象。
適應變化
應用程序可以在各種iOS設備上運行,并且視圖控制器被設計為適應這些設備上不同尺寸的屏幕。并不是使用單獨的視圖控制器來管理不同屏幕上的內容,而是使用內置的適配性支持響應視圖控制器中的尺寸和size class的更改。UIKit發送的通知使我們有機會對用戶界面進行大規模和小規模的更改,而無需更改視圖控制器代碼的其余部分。