視窗和視圖
視窗和視圖是為iPhone應用程式構造使用者介面的可視組件。視窗為內容顯示提供背景平台,而視圖負責絕大部分的內容描畫,並負責響應使用者的互動。雖然本章討論的概念和視窗及視圖都相關聯,但是討論過程更加關注視圖,因為視圖對系統更為重要。
視圖對iPhone應用程式是如此的重要,以至於在一個章節中討論視圖的所有方面是不可能的。本章將關注視窗和視圖的基本屬性、各個屬性之間的關係、以及在應用程式中如何建立和操作這些屬性。本章不討論視圖如何響應觸摸事件或如何描畫定製內容,有關那些主題的更多資訊,請分別參見“事件處理”和“圖形和描畫”部分。
什麼是視窗和視圖?
和Mac OS X一樣,iPhone OS通過視窗和視圖在螢幕上展現圖形內容。雖然視窗和視圖對象之間在兩個平台上有很多相似性,但是具體到每個平台上,它們的作用都有輕微的差別。
UIWindow的作用
和Mac OS X的應用程式有所不同,iPhone應用程式通常只有一個視窗,表示為一個UIWindow類的執行個體。您的應用程式在啟動時建立這個視窗(或者從nib檔案進行裝載),並往視窗中加入一或多個視圖,然後將它顯示出來。視窗顯示出來之後,您很少需要再次引用它。
在iPhone OS中,視窗對象並沒有像關閉框或標題列這樣的視覺裝飾,使用者不能直接對其進行關閉或其它操作。所有對視窗的操作都需要通過其編程介面來實現。應用程式可以藉助視窗對象來進行事件傳遞。視窗對象會持續跟蹤當前的第一響應者對象,並在UIApplication對象提出請求時將事件傳遞它。
還有一件可能讓有經驗的Mac OS X開發人員覺得奇怪的事是UIWindow類的繼承關係。在Mac OS X中,NSWindow的父類是NSResponder;而在iPhone OS中,UIWindow的父類是UIView。因此,視窗在iPhone OS中也是一個視圖對象。不管其起源如何,您通常可以將iPhone OS上的視窗和Mac OS X的視窗同樣對待。也就是說,您通常不必直接操作UIWindow對象中與視圖有關的屬性變數。
在建立應用程式視窗時,您應該總是將其初始的邊框尺寸設定為整個螢幕的大小。如果您的視窗是從nib檔案裝載得到,Interface Builder並不允許建立比螢幕尺寸小的視窗;然而,如果您的視窗是通過編程方式建立的,則必須在建立時傳入期望的邊框矩形。除了螢幕矩形之外,沒有理由傳入其它邊框矩形。螢幕矩形可以通過UIScreen對象來取得,具體代碼如下所示:
UIWindow* aWindow = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
雖然iPhone OS支援將一個視窗疊放在其它視窗的上方,但是您的應用程式永遠不應建立多個視窗。系統自身使用額外的視窗來顯示系統狀態條、重要的警告、以及位於應用程式視窗上方的其它訊息。如果您希望在自己的內容上方顯示警告,可以使用UIKit提供的警告視圖,而不應建立額外的視窗。
UIView是作用視圖
是UIView類的執行個體,負責在螢幕上定義一個矩形地區。在iPhone的應用程式中,視圖在展示使用者介面及響應使用者介面互動方面發揮關鍵作用。每個視圖對象都要負責渲染視圖矩形地區中的內容,並響應該地區中發生的觸碰事件。這一雙重行為意味著視圖是應用程式與使用者互動的重要機制。在一個基於模型-視圖-控制器的應用程式中,視圖對象明顯屬於視圖部分。
除了顯示內容和處理事件之外,視圖還可以用於管理一或多個子視圖。子視圖是指嵌入到另一視圖對象邊框內部的視圖對象,而被嵌入的視圖則被稱為父視圖或超視圖。視圖的這種布局方式被稱為視圖層次,一個視圖可以包含任意數量的子視圖,通過為子視圖添加子視圖的方式,視圖可以實現任意深度的嵌套。視圖在視圖層次中的組織方式決定了在螢幕上顯示的內容,原因是子視圖總是被顯示在其父視圖的上方;這個組織方法還決定了視圖如何響應事件和變化。每個父視圖都負責管理其直接的子視圖,即根據需要調整它們的位置和尺寸,以及響應它們沒有處理的事件。
由於視圖對象是應用程式和使用者互動的主要途徑,所以需要在很多方面發揮作用,下面是其中的一小部分:
描畫和動畫
視圖負責對其所屬的矩形地區進行描畫。
某些視圖屬性變數可以以動畫的形式過渡到新的值。
布局和子視圖管理
視圖管理著一個子視圖列表。
視圖定義了自身相對於其父視圖的尺寸調整行為。
必要時,視圖可以通過代碼調整其子視圖的尺寸和位置。
視圖可以將其座標系統下的點轉換為其它視圖或視窗座標系統下的點。
事件處理
視圖可以接收觸摸事件。
視圖是響應者鏈的參與者。
在iPhone應用程式中,視圖和視圖控制器緊密協作,管理若干方面的視圖行為。視圖控制器的作用是處理視圖的裝載與卸載、處理由於裝置旋轉導致的介面旋轉,以及和用於構建複雜使用者介面的進階導航對象進行互動。更多這方面的資訊請參見“視圖控制器的作用”部分。
本章的大部分內容都著眼於解釋視圖的這些作用,以及說明如何將您自己的定製代碼關聯到現有的UIView行為中。
UIKit的視圖類
UIView類定義了視圖的基本行為,但並不定義其視覺表示。相反,UIKit通過其子類來為像文字框、按鍵、及工具條這樣的標準介面元素定義具體的外觀和行為。圖2-1顯示了所有UIKit視圖類的層次框圖。除了UIView和UIControl類是例外,這個框圖中的大多數視圖都設計為可直接使用,或者和委派物件結合使用。
圖2-1 視圖的類層次
View class hierarchy
這個視圖層次可以分為如下幾個大類:
容器
[內容] 檢視用於增強其它視圖的功能,或者為視圖內容提供額外的視覺分隔。比如,UIScrollView類可以用於顯示因內容太大而無法顯示在一個螢幕上的視圖。UITableView類是UIScrollView類的子類,用於管理資料列表。表格的行可以支援選擇,所以通常也用於層次資料的導航—比如用於挖掘一組有階層的對象。
UIToolbar對象則是一個特殊類型的容器,用於為一或多個類似於按鍵的項提供視覺分組。工具條通常出現在螢幕的底部。Safari、Mail、和Photos程式都使用工具條來顯示一些按鍵,這些按鍵代表經常使用的命令。工具條可以一直顯示,也可以根據應用程式的需要進行顯示。
控制項
控制項用於建立大多數應用程式的使用者介面。控制項是一種特殊類型的視圖,繼承自UIControl超類,通常用於顯示一個具體的值,並處理修改這個值所需要的所有使用者互動。控制項通常使用標準的系統範式(比如目標-動作模式和委託模式)來通知應用程式發生了使用者互動。控制項包括按鍵、文字框、滑塊、和切換開關。
顯示視圖
控制項和很多其它類型的視圖都提供了互動行為,而另外一些視圖則只是用於簡單地顯示資訊。具有這種行為的UIKit類包括UIImageView、 UILabel、UIProgressView、UIActivityIndicatorView。
文本和web視圖
文本和web視圖為應用程式提供更為進階的顯示多行文本的方法。UITextView類支援在捲動區域內顯示和編輯多行文本;而UIWebView類則提供了顯示HTML內容的方法,通過這個類,您可以將圖形和進階的文字格式設定選項整合到應用程式中,並以定製的方式對內容進行布局。
警告視圖和動作表單
警告視圖和動作表單用於即刻取得使用者的注意。它們向使用者顯示一條訊息,同時還有一或多個可選的按鍵,使用者通過這些按鍵來響應訊息。警告視圖和動作表單的功能類似,但是外觀和行為不同。舉例來說,UIAlertView類在螢幕上彈出一個藍色的警告框,而UIActionSheet類則從螢幕的底部滑出動作框。
導航視圖
頁籤條和導航條和視圖控制器結合使用,為使用者提供從一個螢幕到另一個螢幕的導航工具。在使用時,您通常不必直接建立UITabBar和UINavigationBar的項,而是通過恰當的控制器介面或Interface Builder來對其進行配置。
視窗
視窗提供一個描畫內容的表面,是所有其它視圖的根容器。每個應用程式通常都只有一個視窗。更多資訊請參見“UIWindow的作用”部分。
除了視圖之外,UIKit還提供了視圖控制器,用於管理這些對象。更多資訊請參見“視圖控制器的作用”部分。
視圖控制器的作用
運行在iPhone OS上的應用程式在如何組織內容和如何將內容呈現給使用者方面有很多選擇。含有很多內容的應用程式可以將內容分為多個螢幕。在運行時,每個螢幕的背後都是一組視圖對象,負責顯示該螢幕的資料。一個螢幕的視圖後面是一個視圖控制器其作用是管理那些視圖上顯示的資料,並協調它們和應用程式其它部分的關係。
UIViewController類負責建立其管理的視圖及在低記憶體時將它們從內容中移出。視圖控制器還為某些標準的系統行為提供自動響應。比如,在響應裝置方向變化時,如果應用程式支援該方向,視圖控制器可以對其管理的視圖進行尺寸調整,使其適應新的方向。您也可以通過視圖控制器來將新的視圖以模式框的方式顯示在當前視圖的上方。
除了基礎的UIViewController類之外,UIKit還包含很多進階子類,用於處理平台共有的某些進階介面。特別需要提到的是,導航控制器用於顯示多屏具有一定階層的內容;而頁籤條控制器則支援使用者在一組不同的螢幕之間切換,每個螢幕都代表應用程式的一種不同的操作模式。
有關如何通過視圖控制器系統管理使用者介面上視圖的更多資訊,請參見iPhone OS的視圖控制器編程指南。
視圖架構和幾何屬性
由於視圖是iPhone應用程式的焦點對象,所以對視圖與系統其它部分的互動機制有所瞭解是很重要的。UIKit中的標準視圖類為應用程式免費提供相當數量的行為,還提供了一些定義良好的整合點,您可以通過這些整合點來對標準行為進行定製,完成應用程式需要做的工作。
本文的下面部分將解釋視圖的標準行為,並說明哪些地方可以整合您的定製代碼。如果需要特定類的整合點資訊,請參見該類的參考文檔。您可以從UIKit架構參考中取得所有類參考文檔的列表。
視圖互動模型
任何時候,當使用者和您的程式介面進行互動、或者您的代碼以編程的方式進行某些修改時,UIKit內部都會發生一個複雜的事件序列。在事件序列的一些特定的點上,UIKit會調用您的視圖類,使它們有機會代表應用程式進行事件響應。理解這些調用點是很重要的,有助於理解您的視圖對象和系統在哪裡進行結合。圖2-2顯示了從使用者觸擊螢幕到圖形系統更新螢幕內容這一過程的基本事件序列。以編程方式觸發事件的基本步驟與此相同,只是沒有最初的使用者互動。
圖2-2 UIKit和您的視圖對象之間的互動
UIKit interactions with your view objects
下面的步驟說明進一步刨析了圖2-2中的事件序列,解釋了序列的每個階段都發生了什麼,以及應用程式可能如何進行響應。
使用者觸擊螢幕。
硬體將觸擊附隨報告給UIKit架構。
UIKit架構將觸擊資訊封裝為一個UIEvent對象,並派發給恰當的視圖(有關UIKit如何將事件遞送給您的視圖的詳細解釋,請參見“事件的傳遞”部分)。
視圖的事件處理方法可以通過下面的方式來響應事件:
調整視圖或其子視圖的屬性變數(邊框、邊界、透明度等)。
將視圖(或其子視圖)標識為需要修改布局。
將視圖(或其子視圖)標識為布局需要重畫。
將資料發生的變化通報給控制器。
當然,上述的哪些事情需要做及調用什麼方法來完成是由視圖來決定的。
如果視圖被標識為需要重新布局,UIKit就調用視圖的layoutSubviews方法。
您可以在自己的定製視圖中重載這個方法,以便調整子視圖的尺寸和位置。舉例來說,如果一個視圖具有很大的捲動區域,就需要使用幾個子視圖來“平鋪”,而不是建立一個記憶體很可能裝不下的大視圖。在這個方法的實現中,視圖可以隱藏所有不需顯示在螢幕上的子視圖,或者在重新置放之後將它們用於顯示新的內容。作為這個過程的一部分,視圖也可以將用於“平鋪”的子視表徵圖識為需要重畫。
如果視圖的任何部分被標識為需要重畫,UIKit就調用該視圖的drawRect:方法。
UIKit只對那些需要重畫的視圖調用這個方法。在這個方法的實現中,所有視圖都應該儘可能快地重畫指定的地區,且都應該只重畫自己的內容,不應該描畫子視圖的內容。在這個調用點上,視圖不應該嘗試進一步改變其屬性或布局。
所有更新過的視圖都和其它可視內容進行合成,然後發送給圖形硬體進行顯示。
圖形硬體將渲染完成的內容轉移到螢幕。
請注意:上述的更新模型主要適用於採納內建視圖和描畫技術的應用程式。如果您的應用程式使用OpenGL ES來描畫內容,則通常要配置一個全屏的視圖,然後直接在OpenGL的圖形上下文中進行描畫。您的視圖仍然需要處理觸碰事件,但不需要對子視圖進行布局或者實現drawRect:方法。有關OpenGL ES的更多資訊,請參見“用OpenGL ES進行描畫”部分。
基於上述的步驟說明可以看出,UIKit為您自己定製的視圖提供如下主要的結合點:
下面這些事件處理方法:
touchesBegan:withEvent:
touchesMoved:withEvent:
touchesEnded:withEvent:
touchesCancelled:withEvent:
layoutSubviews方法
drawRect:方法
大多數定製視圖通過實現這些方法來得到自己期望的行為。您可能不需要重載所有方法,舉例來說,如果您實現的視圖是固定尺寸的,則可能不需要重載layoutSubviews方法。類似地,如果您實現的視圖只是顯示簡單的內容,比如文本或映像,則通常可以通過簡單地嵌入UIImageView和UILabel對象作為子視圖來避免描畫。
重要的是要記住,這些是主要的結合點,但不是全部。UIView類中有幾個方法的設計目的就是讓子類重載的。您可以通過查閱UIView類參考中的描述來瞭解哪些方法可以被重載。
視圖渲染架構
雖然您通過視圖來表示螢幕上的內容,但是UIView類自身的很多基礎行為卻嚴重依賴於另一個對象。UIKit中每個視圖對象的背後都有一個Core Animation層對象,它是一個CALayer類的執行個體,該類為視圖內容的布局和渲染、以及合成和動畫提供基礎性的支援。
和Mac OS X(在這個平台上Core Animation支援是可選的)不同的是,iPhone OS將Core Animation整合到視圖渲染實現的核心。雖然Core Animation發揮核心作用,但是UIKit在Core Animation上面提供一個透明的介面層,使編程體驗更為流暢。這個透明的介面使開發人員在大多數情況下不必直接存取Core Animation的層,而是通過UIView的方法和屬性聲明取得類似的行為。然而,當UIView類沒有提供您需要的介面時,Core Animation就變得重要了,在那種情況下,您可以深入到Core Animation層,在應用程式中實現一些複雜的渲染。