iPhone应用程序编程指南(窗口和视图)
窗口和視圖
窗口為內容顯示提供背景平臺,而視圖負責絕大部分的內容描畫,并負責響應用戶的交互。
什么是窗口和視圖?
UIWindow的作用
iPhone應用程序通常只有一個窗口,表示為一個UIWindow類的實例。您的應用程序在啟動時創建這個窗口(或者從nib文件進行裝載),并往窗口中加入一或多個視圖,然后將它顯示出來。窗口顯示出來之后,您很少需要再次引用它。
iPhone應用程序通常只有一個窗口,表示為一個UIWindow類的實例。您的應用程序在啟動時創建這個窗口(或者從nib文件進行裝載),并往窗口中加入一或多個視圖,然后將它顯示出來。窗口顯示出來之后,您很少需要再次引用它。
在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的作用
在iPhone的應用程序中,視圖在展示用戶界面及響應用戶界面交互方面發揮關鍵作用。每個視圖對象都要負責渲染視圖矩形區域中的內容,并響應該區域中發生的觸碰事件。這一雙重行為意味著視圖是應用程序與用戶交互的重要機制。
子視圖是指嵌入到另一視圖對象邊框內部的視圖對象,而被嵌入的視圖則被稱為父視圖或超視圖。視圖的這種布局方式被稱為視圖層次
每個父視圖都負責管理其直接的子視圖,即根據需要調整它們的位置和尺寸,以及響應它們沒有處理的事件。
視圖控制器的作用是處理視圖的裝載與卸載、處理由于設備旋轉導致的界面旋轉,以及和用于構建復雜用戶界面的高級導航對象進行交互。
UIKit的視圖類
圖2-1顯示了所有UIKit視圖類的層次框圖。除了UIView和UIControl類是例外,這個框圖中的大多數視圖都設計為可直接使用,或者和委托對象結合使用。
圖2-1??視圖的類層次
視圖控制器的作用
運行在iPhone OS上的應用程序在如何組織內容和如何將內容呈現給用戶方面有很多選擇。含有很多內容的應用程序可以將內容分為多個屏幕。在運行時,每個屏幕的背后都是一組視圖對象,負責顯示該屏幕的數據。一個屏幕的視圖后面是一個視圖控制器其作用是管理那些視圖上顯示的數據,并協調它們和應用程序其它部分的關系。
UIViewController類負責創建其管理的視圖及在低內存時將它們從內容中移出。視圖控制器還為某些標準的系統行為提供自動響應。比如,在響應設備方向變化時,如果應用程序支持該方向,視圖控制器可以對其管理的視圖進行尺寸調整,使其適應新的方向。您也可以通過視圖控制器來將新的視圖以模式框的方式顯示在當前視圖的上方。
?
視圖架構和幾何屬性
視圖交互模型
圖2-2顯示了從用戶觸擊屏幕到圖形系統更新屏幕內容這一過程的基本事件序列。以編程方式觸發事件的基本步驟與此相同,只是沒有最初的用戶交互。
圖2-2??UIKit和您的視圖對象之間的交互
下面的步驟說明進一步刨析了圖2-2中的事件序列,解釋了序列的每個階段都發生了什么,以及應用程序可能如何進行響應。
用戶觸擊屏幕。
硬件將觸擊事件報告給UIKit框架。
UIKit框架將觸擊信息封裝為一個UIEvent對象,并派發給恰當的視圖(有關UIKit如何將事件遞送給您的視圖的詳細解釋,請參見“事件的傳遞”部分)。
視圖的事件處理方法可以通過下面的方式來響應事件:
-
調整視圖或其子視圖的屬性變量(邊框、邊界、透明度等)。
-
將視圖(或其子視圖)標識為需要修改布局。
-
將視圖(或其子視圖)標識為布局需要重畫。
-
將數據發生的變化通報給控制器。
當然,上述的哪些事情需要做及調用什么方法來完成是由視圖來決定的。
如果視圖被標識為需要重新布局,UIKit就調用視圖的layoutSubviews方法。
您可以在自己的定制視圖中重載這個方法,以便調整子視圖的尺寸和位置。舉例來說,如果一個視圖具有很大的滾動區域,就需要使用幾個子視圖來“平鋪”,而不是創建一個內存很可能裝不下的大視圖。在這個方法的實現中,視圖可以隱藏所有不需顯示在屏幕上的子視圖,或者在重新定位之后將它們用于顯示新的內容。作為這個過程的一部分,視圖也可以將用于“平鋪”的子視圖標識為需要重畫。
如果視圖的任何部分被標識為需要重畫,UIKit就調用該視圖的drawRect:方法。
UIKit只對那些需要重畫的視圖調用這個方法。在這個方法的實現中,所有視圖都應該盡可能快地重畫指定的區域,且都應該只重畫自己的內容,不應該描畫子視圖的內容。在這個調用點上,視圖不應該嘗試進一步改變其屬性或布局。
所有更新過的視圖都和其它可視內容進行合成,然后發送給圖形硬件進行顯示。
圖形硬件將渲染完成的內容轉移到屏幕。
請注意:上述的更新模型主要適用于采納內置視圖和描畫技術的應用程序。
基于上述的步驟說明可以看出,UIKit為您自己定制的視圖提供如下主要的結合點:
下面這些事件處理方法:
-
touchesBegan:withEvent:
-
touchesMoved:withEvent:
-
touchesEnded:withEvent:
-
touchesCancelled:withEvent:
layoutSubviews方法
drawRect:方法
大多數定制視圖通過實現這些方法來得到自己期望的行為。
?
視圖渲染架構
UIKit中每個視圖對象的背后都有一個Core Animation層對象,它是一個CALayer類的實例,該類為視圖內容的布局和渲染、以及合成和動畫提供基礎性的支持。
iPhone OS將Core Animation集成到視圖渲染實現的核心。雖然Core Animation發揮核心作用,但是UIKit在Core Animation上面提供一個透明的接口層,使編程體驗更為流暢。這個透明的接口使開發者在大多數情況下不必直接訪問Core Animation的層,而是通過UIView的方法和屬性聲明取得類似的行為。然而,當UIView類沒有提供您需要的接口時,Core Animation就變得重要了,在那種情況下,您可以深入到Core Animation層,在應用程序中實現一些復雜的渲染。
Core Animation基礎
Core Animation利用了硬件加速和架構上的優化來實現快速渲染和實時動畫。當視圖的drawRect:方法首次被調用時,層會將描畫的結果捕捉到一個位圖中,并在隨后的重畫中盡可能使用這個緩存的位圖,以避免調用開銷很大的drawRect:方法。這個過程使Core Animation得以優化合成操作,取得期望的性能。
Core Animation把和視圖對象相關聯的層存儲在一個被稱為層樹的層次結構中。和視圖一樣,層樹中的每個層都只有一個父親,但可以嵌入任意數量的子層。缺省情況下,層樹中對象的組織方式和視圖在視圖層次中的組織方式完全一樣。但是,您可以在層樹中添加層,而不同時添加相應的視圖。當您希望實現某種特殊的視覺效果、而又不需要在視圖上保持這種效果時,就可能需要這種技術。
實際上,層對象是iPhone OS渲染和布局系統的推動力,大多數視圖屬性實際上是其層對象屬性的一個很薄的封裝。當您(直接使用CALayer對象)修改層樹上層對象的屬性時,您所做的改變會立即反映在層對象上。但是,如果該變化觸發了相應的動畫,則可能不會立即反映在屏幕上,而是必須隨著時間的變化以動畫的形式表現在屏幕上。為了管理這種類型的動畫,Core Animation額外維護兩組層對象,我們稱之為表示樹和渲染樹。
表示樹反映的是層在展示給用戶時的當前狀態。假定您對層值的變化實行動畫,則在動畫開始時,表示層反映的是老的值;隨著動畫的進行,Core Animation會根據動畫的當前幀來更新表示樹層的值;然后,渲染樹就和表示樹一起,將變化渲染在屏幕上。由于渲染樹運行在單獨的進程或線程上,所以它所做的工作并不影響應用程序的主運行循環。雖然層樹和表示樹都是公開的,但是渲染樹的接口是私有。
在視圖后面設置層對象對描畫代碼的性能有很多重要的影響。使用層的好處在于視圖的大多數幾何變化都不需要重畫。舉例來說,改變視圖的位置和尺寸并需要重畫視圖的內容,只需簡單地重用層緩存的位圖就可以了。對緩存的內容實行動畫比每次都重畫內容要有效得多。
使用層的缺點在于層是額外的緩存數據,會增加應用程序的內存壓力。如果您的應用程序創建太多的視圖,或者創建多個很大的視圖,則可能很快就會出現內存不夠用的情形。您不用擔心在應用程序中使用視圖,但是,如果有現成的視圖可以重用,就不要創建新的視圖對象。換句話說,您應該設法使內存中同時存在的視圖對象數量最小。
改變視圖的層
在iPhone OS系統中,由于視圖必須有一個與之關聯的層對象,所以UIView類在初始化時會自動創建相應的層。您可以通過視圖的layer屬性訪問這個層,但是不能在視圖創建完成后改變層對象。
如果您希望視圖使用不同類型的層,必須重載其layerClass類方法,并在該方法中返回您希望使用的層對象。使用不同層類的最常見理由是為了實現一個基于OpenGL的應用程序。為了使用OpenGL描畫命令,視圖下面的層必須是CAEAGLLayer類的實例,這種類型的層可以和OpenGL渲染調用進行交互,最終在屏幕上顯示期望的內容。
重要提示:您永遠不應修改視圖層的delegate屬性,該屬性用于存儲一個指向視圖的指針,應該被認為是私有的。類似地,由于一個視圖只能作為一個層的委托,所以您必須避免將它作為其它層對象的委托,否則會導致應用程序崩潰。
?
動畫支持
iPhone OS的每個視圖后面都有一個層對象,這樣做的好處之一是使視圖內容更加易于實現動畫。
UIView類的很多屬性都被設計為可動畫的(animatable)。可動畫的屬性是指當屬性從一個值變為另一個值的時候,可以半自動地支持動畫。您仍然必須告訴UIKit希望執行什么類型的動畫,但是動畫一旦開始,Core Animation就會全權負責。UIView對象中支持動畫的屬性有如下幾個:
-
frame
-
bounds
-
center
-
transform
-
alpha
雖然其它的視圖屬性不直接支持動畫,但是您可以為其中的一部分顯式創建動畫。顯式動畫要求您做很多管理動畫和渲染內容的工作,通過使用Core Animation提供的基礎設施,這些工作仍然可以得到良好的性能。
視圖坐標系統
UIKit中的坐標是基于這樣的坐標系統:以左上角為坐標的原點,原點向下和向右為坐標軸正向。坐標值由浮點數來表示,內容的布局和定位因此具有更高的精度,還可以支持與分辨率無關的特性。
圖2-3??視圖坐標系統
您在編寫界面代碼時,需要知道當前起作用的坐標系統。每個窗口和視圖對象都維護一個自己本地的坐標系統。視圖中發生的所有描畫都是相對于視圖本地的坐標系統。但是,每個視圖的邊框矩形都是通過其父視圖的坐標系統來指定,而事件對象攜帶的坐標信息則是相對于應用程序窗口的坐標系統。
邊框、邊界、和中心的關系
視圖對象通過frame、bounds、和center屬性聲明來跟蹤自己的大小和位置。frame屬性包含一個矩形,即邊框矩形,用于指定視圖相對于其父視圖坐標系統的位置和大小。bounds屬性也包含一個矩形,即邊界矩形,負責定義視圖相對于本地坐標系統的位置和大小。雖然邊界矩形的原點通常被設置為 (0, 0),但這并不是必須的。center屬性包含邊框矩形的中心點。
在代碼中,您可以將frame、bounds、和center屬性用于不同的目的。邊界矩形代表視圖本地的坐標系統,因此,在描畫和事件處理代碼中,經常借助它來取得視圖中發生事件或需要更新的位置。中心點代表視圖的中心,改變中心點一直是移動視圖位置的最好方法。邊框矩形是一個通過bounds和center屬性計算得到的便利值,只有當視圖的變換屬性被設置恒等變換時,邊框矩形才是有效的。
圖2-4顯示了邊框矩形和邊界矩形之間的關系。右邊的整個圖像是從視圖的(0, 0)開始描畫的,但是由于邊界的大小和整個圖像的尺寸不相匹配,所以位于邊界矩形之外的圖像部分被自動裁剪。在視圖和它的父視圖進行合成的時候,視圖在其父視圖中的位置是由視圖邊框矩形的原點決定的。在這個例子中,該原點是(5, 5)。結果,視圖的內容就相對于父視圖的原點向下向右移動相應的尺寸。
圖2-4??視圖的邊框和邊界之間的關系
如果沒有經過變換,視圖的位置和大小就由上述三個互相關聯的屬性決定的。當您在代碼中通過initWithFrame:方法創建一個視圖對象時,其frame屬性就會被設置。該方法同時也將bounds矩形的原點初始化為(0.0, 0.0),大小則和視圖的邊框相同。然后center屬性會被設置為邊框的中心點。
雖然您可以分別設置這些屬性的值,但是設置其中的一個屬性會引起其它屬性的改變,具體關系如下:
-
當您設置frame屬性時,bounds屬性的大小會被設置為與frame屬性的大小相匹配的值,center屬性也會被調整為與新的邊框中心點相匹配的值。
-
當您設置center屬性時,frame的原點也會隨之改變。
-
當您設置bounds矩形的大小時,frame矩形的大小也會隨之改變。
您可以改變bounds的原點而不影響其它兩個屬性。當您這樣做時,視圖會顯示您標識的圖形部分。在圖2-4中,邊界的原點被設置為(0.0, 0.0)。在圖2-5中,該原點被移動到(8.0, 24.0)。結果,顯示出來的是視圖圖像的不同部分。但是,由于邊框矩形并沒有改變,新的內容在父視圖中的位置和之前是一樣的。
圖2-5??改變視圖的邊界
請注意:缺省情況下,視圖的邊框并不會被父視圖的邊框裁剪。如果您希望讓一個視圖裁剪其子視圖,需要將其clipsToBounds屬性設置為YES。
坐標系統變換
在視圖的drawRect:方法中常常借助坐標系統變換來進行描畫。而在iPhone OS系統中,您還可以用它來實現視圖的某些視覺效果。舉例來說,UIView類中包含一個transform屬性聲明,您可以通過它來對整個視圖實行各種類型的平移、比例縮放、和變焦縮放效果。缺省情況下,這個屬性的值是一個恒等變換,不會改變視圖的外觀。在加入變換之前,首先要得到該屬性中存儲的CGAffineTransform結構,用相應的Core Graphics函數實行變換,然后再將修改后的變換結構重新賦值給視圖的transform屬性。
請注意:當您將變換應用到視圖時,所有執行的變換都是相對于視圖的中心點。
平移一個視圖會使其所有的子視圖和視圖本身的內容一起移動。由于子視圖的坐標系統是繼承并建立在這些變化的基礎上的,所以比例縮放也會影響子視圖的描畫。有關如何控制視圖內容縮放的更多信息,請參見“內容模式和比例縮放”部分。
重要提示:如果transform屬性的值不是恒等變換,則frame屬性的值就是未定義的,必須被忽略。在設置變換屬性之后,請使用bounds和center屬性來獲取視圖的位置和大小。
?
有關如何在drawRect:方法中使用變換的信息,請參見“坐標和坐標變換”部分;有關用于修改CGAffineTransform結構的函數,則請參見CGAffineTransform參考。
內容模式與比例縮放
當您改變視圖的邊界,或者將一個比例因子應用到視圖的transform屬性聲明時,邊框矩形會發生等量的變化。根據內容模式的不同,視圖的內容也可能被縮放或重新定位,以反映上述的變化。視圖的contentMode屬性決定了邊界變化和縮放操作作用到視圖上產生的效果。缺省情況下,這個屬性的值被設置為UIViewContentModeScaleToFill,意味著視圖內容總是被縮放,以適應新的邊框尺寸。作為例子,圖2-6顯示了當視圖的水平縮放因子放大一倍時產生的效果。
圖2-6?使用scale-to-fill內容模式縮放視圖
視圖內容的縮放僅在首次顯示視圖的時候發生,渲染后的內容會被緩存在視圖下面的層上。當邊界或縮放因子發生變化時,UIKit并不強制視圖進行重畫,而是根據其內容模式決定如何顯示緩存的內容。圖2-7比較了在不同的內容模式下,改變視圖邊界或應用不同的比例縮放因子時產生的結果。
圖2-7??內容模式比較
對視圖應用一個比例縮放因子總是會使其內容發生縮放,而邊界的改變在某些內容模式下則不會發生同樣的結果。不同的UIViewContentMode常量(比如UIViewContentModeTop和UIViewContentModeBottomRight)可以使當前的內容在視圖的不同角落或沿著視圖的不同邊界顯示,還有一種模式可以將內容顯示在視圖的中心。在這些模式的作用下,改變邊界矩形只會簡單地將現有的視圖內容移動到新的邊界矩形中對應的位置上。
當您希望在應用程序中實現尺寸可調整的控件時,請務必考慮使用內容模式。這樣做可以避免控件的外觀發生變形,以及避免編寫定制的描畫代碼。按鍵和分段控件(segmented control)特別適合基于內容模式的描畫。它們通常使用幾個圖像來創建控件外觀。除了有兩個固定尺寸的蓋帽圖像之外,按鍵可以通過一個可伸展的、寬度只有一個像素的中心圖像來實現水平方向的尺寸調整。它將每個圖像顯示在自己的圖像視圖中,而將可伸展的中間圖像的內容模式設置為UIViewContentModeScaleToFill,使得在尺寸調整時兩端的外觀不會變形。更為重要的是,每個圖像視圖的關聯圖像都可以由Core Animation來緩存,因此不需要編寫描畫代碼就可以支持動畫,從而使大大提高了性能。
內容模式通常有助于避免視圖內容的描畫,但是當您希望對縮放和尺寸調整過程中的視圖外觀進行特別的控制時,也可以使用UIViewContentModeRedraw模式。將視圖的內容模式設置為這個值可以強制Core Animation使視圖的內容失效,并調用視圖的drawRect:方法,而不是自動進行縮放或尺寸調整。
自動尺寸調整行為
當您改變視圖的邊框矩形時,其內嵌子視圖的位置和尺寸往往也需要改變,以適應原始視圖的新尺寸。如果視圖的autoresizesSubviews屬性聲明被設置為YES,則其子視圖會根據autoresizingMask屬性的值自動進行尺寸調整。簡單配置一下視圖的自動尺寸調整掩碼常常就能使應用程序得到合適的行為;否則,應用程序就必須通過重載layoutSubviews方法來提供自己的實現。
設置視圖的自動尺寸調整行為的方法是通過位OR操作符將期望的自動尺寸調整常量連結起來,并將結果賦值給視圖的autoresizingMask屬性。表2-1列舉了自動尺寸調整常量,并描述這些常量如何影響給定視圖的尺寸和位置。舉例來說,如果要使一個視圖和其父視圖左下角的相對位置保持不變,可以加入UIViewAutoresizingFlexibleRightMargin和UIViewAutoresizingFlexibleTopMargin常量,并將結果賦值給autoresizingMask屬性。當同一個軸向有多個部分被設置為可變時,尺寸調整的裕量會被平均分配到各個部分上。
| UIViewAutoresizingNone | 這個常量如果被設置,視圖將不進行自動尺寸調整。 |
| UIViewAutoresizingFlexibleHeight | 這個常量如果被設置,視圖的高度將和父視圖的高度一起成比例變化。否則,視圖的高度將保持不變。 |
| UIViewAutoresizingFlexibleWidth | 這個常量如果被設置,視圖的寬度將和父視圖的寬度一起成比例變化。否則,視圖的寬度將保持不變。 |
| UIViewAutoresizingFlexibleLeftMargin | 這個常量如果被設置,視圖的左邊界將隨著父視圖寬度的變化而按比例進行調整。否則,視圖和其父視圖的左邊界的相對位置將保持不變。 |
| UIViewAutoresizingFlexibleRightMargin | 這個常量如果被設置,視圖的右邊界將隨著父視圖寬度的變化而按比例進行調整。否則,視圖和其父視圖的右邊界的相對位置將保持不變。 |
| UIViewAutoresizingFlexibleBottomMargin | 這個常量如果被設置,視圖的底邊界將隨著父視圖高度的變化而按比例進行調整。否則,視圖和其父視圖的底邊界的相對位置將保持不變。 |
| UIViewAutoresizingFlexibleTopMargin | 這個常量如果被設置,視圖的上邊界將隨著父視圖高度的變化而按比例進行調整。否則,視圖和其父視圖的上邊界的相對位置將保持不變。 |
圖2-8為這些常量值的位置提供了一個圖形表示。如果這些常量之一被省略,則視圖在相應方向上的布局就被固定;如果某個常量被包含在掩碼中,在該方向的視圖布局就就靈活的。
圖2-8??視圖的自動尺寸調整掩碼常量
如果視圖的autoresizesSubviews屬性被設置為NO,則該視圖的直接子視圖的所有自動尺寸調整行為將被忽略。類似地,如果一個子視圖的自動尺寸調整掩碼被設置為UIViewAutoresizingNone,則該子視圖的尺寸將不會被調整,因而其直接子視圖的尺寸也不會被調整。
請注意:為了使自動尺寸調整的行為正確,視圖的transform屬性必須設置為恒等變換;其它變換下的尺寸自動調整行為是未定義的。
自動尺寸調整行為可以適合一些布局的要求,但是如果您希望更多地控制視圖的布局,可以在適當的視圖類中重載layoutSubviews方法。
?
創建和管理視圖層次
重要提示:在內存管理方面,可以將子視圖考慮為其它的集合對象。特別是當您通過addSubview:方法將一個視圖作為子視圖插入時,父視圖會對其進行保持操作。反過來,當您通過removeFromSuperview方法將子視圖從父視圖移走時,子視圖會被自動釋放。在將視圖加入視圖層次之后釋放該對象可以避免多余的保持操作,從而避免內存泄露。
當您為某個視圖添加子視圖時,UIKit會向相應的父子視圖發送幾個消息,通知它們當前發生的狀態變化。您可以在自己的定制視圖中對諸如willMoveToSuperview:、willMoveToWindow:、willRemoveSubview:、didAddSubview:、didMoveToSuperview、和didMoveToWindow這樣的方法進行重載,以便在事件發生的前后進行必要的處理,并根據發生的變化更新視圖的狀態信息。
對于當前被顯示在屏幕上的視圖,窗口對象通常是整個視圖層次的根視圖。
您可以通過視圖的window屬性來取得指向其父窗口(如果有的話)的指針,如果視圖還沒有被鏈接到窗口上,則該屬性會被設置為nil。
視圖層次中的坐標轉換
很多時候,特別是處理事件的時候,應用程序可能需要將一個相對于某邊框的坐標值轉換為相對于另一個邊框的值。例如,觸摸事件通常使用基于窗口指標系統的坐標值來報告事件發生的位置,但是視圖對象需要的是相對于視圖本地坐標的位置信息,兩者可能是不一樣的。UIView類定義了下面這些方法,用于在不同的視圖本地坐標系統之間進行坐標轉換:
-
convertPoint:fromView:
-
convertRect:fromView:
-
convertPoint:toView:
-
convertRect:toView:
convert...:fromView:方法將指定視圖的坐標值轉換為視圖本地坐標系統的坐標值;convert...:toView:方法則將視圖本地坐標系統的坐標值轉換為指定視圖坐標系統的坐標值。如果傳入nil作為視圖引用參數的值,則上面這些方法會將視圖所在窗口的坐標系統作為轉換的源或目標坐標系統。
除了UIView的轉換方法之外,UIWindow類也定義了幾個轉換方法。這些方法和UIView的版本類似,只是UIView定義的方法將視圖本地坐標系統作為轉換的源或目標坐標系統,而UIWindow的版本則使用窗口坐標系統。
-
convertPoint:fromWindow:
-
convertRect:fromWindow:
-
convertPoint:toWindow:
-
convertRect:toWindow:
當參與轉換的視圖沒有被旋轉,或者被轉換的對象僅僅是點的時候,坐標轉換相當直接。如果是在旋轉之后的視圖之間轉換矩形或尺寸數據,則其幾何結構必須經過合理的改變,才能得到正確的結果坐標。在對矩形結構進行轉換時,UIView類假定您希望保證原來的屏幕區域被覆蓋,因此轉換后的矩形會被放大,其結果是使放大后的矩形(如果放在對應的視圖中)可以完全覆蓋原來的矩形區域。圖2-11顯示了將rotatedView對象的坐標系統中的矩形轉換到其超類(outerView)坐標系統的結果。
圖2-11??對旋轉后視圖中的值進行轉換
對于尺寸信息,UIView簡單地將它處理為分別相對于源視圖和目標視圖(0.0, 0.0)點的偏移量。雖然偏移量保持不變,但是相對于坐標軸的差額會隨著視圖的旋轉而移動。在轉換尺寸數據時,UIKit總是返回正的數值。
標識視圖
您可以通過UIView的viewWithTag:方法來檢索標識過的視圖。該方法從消息的接收者自身開始,通過深度優先的方法來檢索接收者的子視圖。
?
在運行時修改視圖
實現視圖動畫
動畫塊從調用UIView的beginAnimations:context:類方法開始,而以調用commitAnimations類方法作為結束。在這兩個調用之間,您可以配置動畫的參數和改變希望實行動畫的屬性值。一旦調用commitAnimations方法,UIKit就會開始執行動畫,即把給定屬性從當前值到新值的變化過程用動畫表現出來。動畫塊可以被嵌套,但是在最外層的動畫塊提交之前,被嵌套的動畫不會被執行。
表2-2列舉了UIView類中支持動畫的屬性。
| frame | 視圖的邊框矩形,位于父視圖的坐標系中。 |
| bounds | 視圖的邊界矩形,位于視圖的坐標系中。 |
| center | 邊框的中心,位于父視圖的坐標系中。 |
| transform | 視圖上的轉換矩陣,相對于視圖邊界的中心。 |
| alpha | 視圖的alpha值,用于確定視圖的透明度。 |
配置動畫的參數
除了在動畫塊中改變屬性值之外,您還可以對其它參數進行配置,以確定您希望得到的動畫行為。為此,您可以調用下面這些UIView的類方法:
-
用setAnimationStartDate:方法來設置動畫在commitAnimations方法返回之后的發生日期。缺省行為是使動畫立即在動畫線程中執行。
-
用setAnimationDelay:方法來設置實際發生動畫和commitAnimations方法返回的時間點之間的間隔。
-
用setAnimationDuration:方法來設置動畫持續的秒數。
-
用setAnimationCurve:方法來設置動畫過程的相對速度,比如動畫可能在啟示階段逐漸加速,而在結束階段逐漸減速,或者整個過程都保持相同的速度。
-
用setAnimationRepeatCount:方法來設置動畫的重復次數。
-
用setAnimationRepeatAutoreverses:方法來指定動畫在到達目標值時是否自動反向播放。您可以結合使用這個方法和setAnimationRepeatCount:方法,使各個屬性在初始值和目標值之間平滑切換一段時間。
commitAnimations類方法在調用之后和動畫開始之前立刻返回。UIKit在一個獨立的、和應用程序的主事件循環分離的線程中執行動畫。commitAnimations方法將動畫發送到該線程,然后動畫就進入線程中的隊列,直到被執行。缺省情況下,只有在當前正在運行的動畫塊執行完成后,Core Animation才會啟動隊列中的動畫。但是,您可以通過向動畫塊中的setAnimationBeginsFromCurrentState:類方法傳入YES來重載這個行為,使動畫立即啟動。這樣做會停止當前正在執行的動畫,而使新動畫在當前狀態下開始執行。
缺省情況下,所有支持動畫的屬性在動畫塊中發生的變化都會形成動畫。如果您希望讓動畫塊中發生的某些變化不產生動畫效果,可以通過setAnimationsEnabled:方法來暫時禁止動畫,在完成修改后才重新激活動畫。在調用setAnimationsEnabled:方法并傳入NO值之后,所有的改變都不會產生動畫效果,直到用YES值再次調用這個方法或者提交整個動畫塊時,動畫才會恢復。您可以用areAnimationsEnabled方法來確定當前是否激活動畫。
配置動畫的委托
您可以為動畫塊分配一個委托,并通過該委托接收動畫開始和結束的消息。當您需要在動畫開始前和結束后立即執行其它任務時,可能就需要這樣做。您可以通過UIView的setAnimationDelegate:類方法來設置委托,并通過setAnimationWillStartSelector:和setAnimationDidStopSelector:方法來指定接收消息的選擇器方法。消息處理方法的形式如下:
| - (void)animationWillStart:(NSString *)animationID context:(void *)context; |
| - (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context; |
上面兩個方法的animationID和context參數和動畫塊開始時傳給beginAnimations:context:方法的參數相同:
-
animationID?- 應用程序提供的字符串,用于標識一個動畫塊中的動畫。
-
context?- 也是應用程序提供的對象,用于向委托對象傳遞額外的信息。
setAnimationDidStopSelector:選擇器方法還有一個參數—即一個布爾值。如果動畫順利完成,沒有被其它動畫取消或停止,則該值為YES。
響應布局的變化
任何時候,當視圖的布局發生改變時,UIKit會激活每個視圖的自動尺寸調整行為,然后調用各自的layoutSubviews方法,使您有機會進一步調整子視圖的幾何尺寸。下面列舉的情形都會引起視圖布局的變化:
-
視圖邊界矩形的尺寸發生變化。
-
滾動視圖的內容偏移量—也就是可視內容區域的原點—發生變化。
-
和視圖關聯的轉換矩陣發生變化。
-
和視圖層相關聯的Core Animation子層組發生變化。
-
您的應用程序調用視圖的setNeedsLayout或layoutIfNeeded方法來強制進行布局。
-
您的應用程序調用視圖背后的層對象的setNeedsLayout方法來強制進行布局。
子視圖的初始布局由視圖的自動尺寸調整行為來負責。
有些時候,您可能希望通過layoutSubviews方法來手工調整子視圖的布局,而不是完全依賴自動尺寸調整行為。舉例來說,如果您要實現一個由幾個子視圖元素組成的定制控件,則可以通過手工調整子視圖來精確控制控件在一定尺寸范圍內的外觀。還有,如果一個視圖表示的滾動內容區域很大,可以選擇將內容顯示為一組平鋪的子視圖,在滾動過程中,可以回收離開屏幕邊界的視圖,并在填充新內容后將它重新定位,使它成為下一個滾入屏幕的視圖。
請注意:您也可以用layoutSubviews方法來調整作為子層鏈接到視圖層的定制CALayer對象。您可以通過對隱藏在視圖后面的層層次進行管理,實現直接基于Core Animation的高級動畫。有關如何通過Core Animation管理層層次的更多信息,請參見Core Animation編程指南。
重畫視圖的內容
有些時候,應用程序數據模型的變化會影響到相應的用戶界面。為了反映這些變化,您可以將相應的視圖標識為需要刷新(通過調用setNeedsDisplay或setNeedsDisplayInRect:方法)。和簡單創建一個圖形上下文并進行描畫相比,將視圖標識為需要刷新的方法使系統有機會更有效地執行描畫操作。舉例來說,如果您在某個運行周期中將一個視圖的幾個區域標識為需要刷新,系統就會將這些需要刷新的區域進行合并,并最終形成一個drawRect:方法的調用。結果,只需要創建一個圖形上下文就可以描畫所有這些受影響的區域。這個做法比連續快速創建幾個圖形上下文要有效得多。
實現drawRect:方法的視圖總是需要檢查傳入的矩形參數,并用它來限制描畫操作的范圍。因為描畫是開銷相對昂貴的操作,以這種方式來限制描畫是提高性能的好方法。
缺省情況下,視圖在幾何上的變化并不自動導致重畫。相反,大多數幾何變化都由Core Animation來自動處理。具體來說,當您改變視圖的frame、bounds、center、或transform屬性時,Core Animation會將相應的幾何變化應用到與視圖層相關聯的緩存位圖上。在很多情況下,這種方法是完全可以接受的,但是如果您發現結果不是您期望得到的,則可以強制UIKit對視圖進行重畫。為了避免Core Animation自動處理幾何變化,您可以將視圖的contentMode屬性聲明設置為UIViewContentModeRedraw。更多有關內容模式的信息,請參見“內容模式和比例縮放”部分。
隱藏視圖
您可以通過改變視圖的hidden屬性聲明來隱藏或顯示視圖。將這個屬性設置為YES會隱藏視圖,設置為NO則可以顯示視圖。對一個視圖進行隱藏會同時隱藏其內嵌的所有子視圖,就好象它們自己的hidden屬性也被設置一樣。
當您隱藏一個視圖時,該視圖仍然會保留在視圖層次中,但其內容不會被描畫,也不會接收任何觸摸事件。由于隱藏視圖仍然存在于視圖層次中,所以會繼續參與自動尺寸調整和其它布局操作。如果被隱藏的視圖是當前的第一響應者,則該視圖會自動放棄其自動響應者的狀態,但目標為第一響應者的事件仍然會傳遞給隱藏視圖。有關響應者鏈的更多信息,請參見“響應者對象和響應者鏈”部分。
?
創建一個定制視圖
初始化您的定制視圖
程序清單2-2??初始化一個視圖的子類
| - (id)initWithFrame:(CGRect)aRect { |
| self = [super initWithFrame:aRect]; |
| if (self) { |
| // setup the initial properties of the view |
| ... |
| } |
| return self; |
| } |
?
描畫您的視圖內容
當您改變視圖內容時,可以通過setNeedsDisplay或setNeedsDisplayInRect:方法來將需要重畫的部分通知給系統。在應用程序返回運行循環之后,會對所有的描畫請求進行合并,計算界面中需要被更新的部分;之后就開始遍歷視圖層次,向需要更新的視圖發送drawRect:消息。遍歷的起點是視圖層次的根視圖,然后從后往前遍歷其子視圖。在可視邊界內顯示定制內容的視圖必須實現其drawRect:方法,以便對該內容進行渲染。
在調用視圖的drawRect:方法之前,UIKit會為其配置描畫的環境,即創建一個圖形上下文,并調整其坐標系統和裁剪區,使之和視圖的坐標系統及邊界相匹配。因此,在您的drawRect:方法被調用時,您可以使用UIKit的類和函數、Quartz的函數、或者使用兩者相結合的方法來直接進行描畫。需要的話,您可以通過UIGraphicsGetCurrentContext函數來取得當前圖形上下文的指針,實現對它的訪問。
程序清單2-3顯示了drawRect:方法的一個簡單實現,即在視圖邊界描畫一個10像素寬的紅色邊界。由于UIKit描畫操作的實現也是基于Quartz,所以您可以像下面這樣混合使用不同的描畫調用來得到期望的結果。
程序清單2-3??一個描畫方法
| - (void)drawRect:(CGRect)rect { |
| CGContextRef context = UIGraphicsGetCurrentContext(); |
| CGRect myFrame = self.bounds; |
| ? |
| CGContextSetLineWidth(context, 10); |
| ? |
| [[UIColor redColor] set]; |
| UIRectFrame(myFrame); |
| } |
如果您能確定自己的描畫代碼總是以不透明的內容覆蓋整個視圖的表面,則可以將視圖的opaque屬性聲明設置為YES,以提高描畫代碼的總體效率。當您將視圖標識為不透明時,UIKit會避免對該視圖正下方的內容進行描畫。這不僅減少了描畫開銷的時間,而且減少內容合成需要的工作。然而,只有當您能確定視圖提供的內容為不透明時,才能將這個屬性設置為YES;如果您不能保證視圖內容總是不透明,則應該將它設置為NO。
提高描畫性能(特別是在滾動過程)的另一個方法是將視圖的clearsContextBeforeDrawing屬性設置為NO。當這個屬性被設置為YES時,UIKIt會在調用drawRect:方法之前,把即將被該方法更新的區域填充為透明的黑色。將這個屬性設置為NO可以取消相應的填充操作,而由應用程序負責完全重畫傳給drawRect:方法的更新矩形中的部分。這樣的優化在滾動過程中通常是一個好的折衷。?
響應事件
UIView類是UIResponder的一個子類,因此能夠接收用戶和視圖內容交互時產生的觸摸事件。觸摸事件從發生觸摸的視圖開始,沿著響應者鏈進行傳遞,直到最后被處理。視圖本身就是響應者,是響應者鏈的參與者,因此可以收到所有關聯子視圖派發給它們的觸摸事件。
處理觸摸事件的視圖通常需要實現下面的所有方法,更多細節請參見“事件處理”部分:
-
touchesBegan:withEvent:
-
touchesMoved:withEvent:
-
touchesEnded:withEvent:
-
touchesCancelled:withEvent:
請記住,在缺省情況下,視圖每次只響應一個觸摸動作。如果用戶將第二個手指放在屏幕上,系統會忽略該觸摸事件,而不會將它報告給視圖對象。如果您希望在視圖的事件處理器方法中跟蹤多點觸摸手勢,則需要重新激活多點觸摸事件,具體方法是將視圖的multipleTouchEnabled屬性聲明設置為YES。
某些視圖,比如標簽和圖像視圖,在初始狀態下完全禁止事件處理。您可以通過改變視圖的userInteractionEnabled屬性值來控制視圖是否可以對事件進行處理。當某個耗時很長的操作被掛起時,您可以暫時將這個屬性設置為NO,使用戶無法對視圖的內容進行操作。為了阻止事件到達您的視圖,還可以使用UIApplication對象的beginIgnoringInteractionEvents和endIgnoringInteractionEvents方法。這些方法影響的是整個應用程序的事件分發,而不僅僅是某個視圖。
在處理觸摸事件時,UIKit會通過UIView的hitTest:withEvent:和pointInside:withEvent:方法來確定觸摸事件是否發生在指定的視圖上。雖然很少需要重載這些方法,但是您可以通過重載來使子視圖無法處理觸摸事件。
視圖對象的清理
如果您的視圖類分配了任何內存、存儲了任何對象的引用、或者持有在釋放視圖時也需要被釋放的資源,則必須實現其dealloc方法。當您的視圖對象的保持數為零、且視圖本身即將被解除分配時,系統會調用其dealloc方法。您在這個方法的實現中應該釋放視圖持有的對象和資源,然后調用超類的實現,如程序程序清單2-4所示。
程序清單2-4??實現dealloc方法
| - (void)dealloc { |
| // Release a retained UIColor object |
| [color release]; |
| ? |
| // Call the inherited implementation |
| [super dealloc]; |
| } |
轉載于:https://www.cnblogs.com/Piosa/archive/2012/02/06/2339450.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的iPhone应用程序编程指南(窗口和视图)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iPhone开发进阶(1) --- 深入
- 下一篇: Jquery ValidateEngin