UIView如何管理它的子視圖

UIView提供了不少創建和管理視圖的方法。編程

一、添加視圖數組

     insertSubview:atIndex:   //放在子視圖數組的具體索引位置緩存

     insertSubview:aboveSubview:   //某個子視圖前面架構

     insertSubview:aboveSubview:   //某個子視圖前面app

二、從新排序和刪除子視圖框架

      [parentView exchangeSubviewAtIndex:i withSubviewAtIndex:j]//交換兩個視圖的位置ide

      bringSubviewToFront:和sendSubviewToBack://將子視圖提早活置後函數

      [childView removeSuperview]//刪除某個視圖的子視圖工具

三、視圖回調佈局

   某個視圖的層次一改變,該視圖就會收到一次回調。

   a、調用addSubivew:成功後會給該視圖發送didAddSubivew:回調,觸發UIView的子類在心增視圖時執行其餘操做。

   b、didMoveToSuperview:會通知相關視圖他們的上級視圖已經變化。

   c、視圖移動前會發出willMoveToSuperview:回調

   d、didMoveToWindow:回調和didMoveToSuperview:類似,從命名上能看出其區別。

   e、willMoveToWindow:在視圖移動前發出的回調。

    f、willRemoveToSubview:回調通知父視圖子視圖即將被刪除


UIView掌管直接屏幕繪圖。他的drawRect:方法提供一種低級方式來直接繪製內容,容許使用Quartz 2D調用建立和顯示任意元素,可將這兩個元素結合起來共同構建具體、可操做的界面。

當用戶觸摸屏幕時,Touchview類收集一系列點,在每一個觸摸移動之處,touchesMoved:WithEvent:方法調用setNeedsDisplay。這又會觸發對drawRect:方法的調用,其中視圖將這些點繪製成線段來建立一個可視屏幕路徑。

Core Animation基礎

Core Animation利用了硬件加速和架構上的優化來實現快速渲染和實時動畫。當視圖的drawRect:方法首次被調用時,層會將描畫的結果捕捉到一個位圖中,並在隨後的重畫中儘量使用這個緩存的位圖,以免調用開銷很大的drawRect:方法。這個過程使Core Animation得以優化合成操做,取得指望的性能。

Core Animation把和視圖對象相關聯的層存儲在一個被稱爲層樹的層次結構中。和視圖一 樣,層樹中的每一個層都只有一個父親,但能夠嵌入任意數量的子層。缺省狀況下,層樹中對象的組織方式和視圖在視圖層次中的組織方式徹底同樣。可是,您能夠在 層樹中添加層,而不一樣時添加相應的視圖。當您但願實現某種特殊的視覺效果、而又不須要在視圖上保持這種效果時,就可能須要這種技術。

實際上,層對象是iPhone OS渲染和佈局系統的推進力,大多數視圖屬性其實是其層對象屬性的一個很薄的封裝。當您(直接使用CALayer對 象)修改層樹上層對象的屬性時,您所作的改變會當即反映在層對象上。可是,若是該變化觸發了相應的動畫,則可能不會當即反映在屏幕上,而是必須隨着時間的 變化以動畫的形式表如今屏幕上。爲了管理這種類型的動畫,Core Animation額外維護兩組層對象,咱們稱之爲表示樹渲染樹

表示樹反映的是層在展現給用戶時的當前狀態。假定您對層值的變化實行動畫,則在動畫開始時,表示層反映的是老的值;隨着動畫的進 行,Core Animation會根據動畫的當前幀來更新表示樹層的值;而後,渲染樹就和表示樹一塊兒,將變化渲染在屏幕上。因爲渲染樹運行在單獨的進程或線程上,因此 它所作的工做並不影響應用程序的主運行循環。雖然層樹和表示樹都是公開的,可是渲染樹的接口是私有。

在視圖後面設置層對象對描畫代碼的性能有不少重要的影響。使用層的好處在於視圖的大多數幾何變化都不須要重畫。舉例來講,改變視圖的位置和尺寸並須要重畫視圖的內容,只需簡單地重用層緩存的位圖就能夠了。對緩存的內容實行動畫比每次都重畫內容要有效得多。

使用層的缺點在於層是額外的緩存數據,會增長應用程序的內存壓力。若是您的應用程序建立太多的視圖,或者建立多個很大的視圖,則可能很 快就會出現內存不夠用的情形。您不用擔憂在應用程序中使用視圖,可是,若是有現成的視圖能夠重用,就不要建立新的視圖對象。換句話說,您應該設法使內存中 同時存在的視圖對象數量最小。

有關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提供的基礎設施,這些工做仍然能夠獲得良好的性能。

有關如何經過UIView類建立動畫的更多信息,請參見「實現視圖動畫」部分;有關如何建立顯式動畫的更多信息,則請參見Core Animation編程指南

視圖座標系統

UIKit中的座標是基於這樣的座標系統:以左上角爲座標的原點,原點向下和向右爲座標軸正向。座標值由浮點數來表示,內容的佈局和定位所以具備更高的精度,還能夠支持與分辨率無關的特性。圖2-3顯示了這個相對於屏幕的座標系統,這個座標系統同時也用於UIWindowUIView類。視圖座標系統的方向和Quartz及Mac OS X使用的缺省方向不一樣,選擇這個特殊的方向是爲了使佈局用戶界面上的控件及內容更加容易。

圖2-3  視圖座標系統

您在編寫界面代碼時,須要知道當前起做用的座標系統。每一個窗口和視圖對象都維護一個本身本地的座標系統。視圖中發生的全部描畫都是相對 於視圖本地的座標系統。可是,每一個視圖的邊框矩形都是經過其父視圖的座標系統來指定,而事件對象攜帶的座標信息則是相對於應用程序窗口的座標系統。爲了方 便,UIWindowUIView類都提供了一些方法,用於在不一樣對象之間進行座標系統的轉換。

雖然Quartz使用的座標系統不以左上角爲原點,可是對於不少Quartz調用來講,這並非問題。在調用視圖的drawRect:方法以前,UIKit會自動對描畫環境進行配置,使左上角成爲座標系統的原點,在這個環境中發生的Quartz調用均可以正確地在視圖中描畫。您惟一須要考慮不一樣座標系統之間差異的場合是當您自行經過Quartz創建描畫環境的時候。

更多有關座標系統、Quartz、和描畫的通常信息,請參見「圖形和描畫」部分。

邊框、邊界、和中心的關係

視圖對象經過framebounds、和center屬性聲明來跟蹤本身的大小和位置。frame屬性包含一個矩形,即邊框矩形,用於指定視圖相對於其父視圖座標系統的位置和大小。bounds屬性也包含一個矩形,即邊界矩形,負責定義視圖相對於本地座標系統的位置和大小。雖然邊界矩形的原點一般被設置爲 (0, 0),但這並非必須的。center屬性包含邊框矩形的中心點

在代碼中,您能夠將framebounds、和center屬性用於不一樣的目的。邊界矩形表明視圖本地的座標系統,所以,在描畫和事件處理代碼中,常常藉助它來取得視圖中發生事件或須要更新的位置。中心點表明視圖的中心,改變中心點一直是移動視圖位置的最好方法。邊框矩形是一個經過boundscenter屬性計算獲得的便利值,只有當視圖的變換屬性被設置恆等變換時,邊框矩形纔是有效的。

圖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屬性的值就是未定義的,必須被忽略。在設置變換屬性以後,請使用boundscenter屬性來獲取視圖的位置和大小。

有關如何在drawRect:方法中使用變換的信息,請參見「座標和座標變換」部分;有關用於修改CGAffineTransform結構的函數,則請參見CGAffineTransform參考

內容模式與比例縮放

當您改變視圖的邊界,或者將一個比例因子應用到視圖的transform屬性聲明時,邊框矩形會發生等量的變化。根據內容模式的不一樣,視圖的內容也可能被縮放或從新定位,以反映上述的變化。視圖的contentMode屬性決定了邊界變化和縮放操做做用到視圖上產生的效果。缺省狀況下,這個屬性的值被設置爲UIViewContentModeScaleToFill,意味着視圖內容老是被縮放,以適應新的邊框尺寸。做爲例子,圖2-6顯示了當視圖的水平縮放因子放大一倍時產生的效果。

圖2-6 使用scale-to-fill內容模式縮放視圖

視圖內容的縮放僅在首次顯示視圖的時候發生,渲染後的內容會被緩存在視圖下面的層上。當邊界或縮放因子發生變化時,UIKit並不強制視圖進行重畫,而是根據其內容模式決定如何顯示緩存的內容。圖2-7比較了在不一樣的內容模式下,改變視圖邊界或應用不一樣的比例縮放因子時產生的結果。

圖2-7  內容模式比較

對視圖應用一個比例縮放因子老是會使其內容發生縮放,而邊界的改變在某些內容模式下則不會發生一樣的結果。不一樣的UIViewContentMode常量(好比UIViewContentModeTopUIViewContentModeBottomRight)可使當前的內容在視圖的不一樣角落或沿着視圖的不一樣邊界顯示,還有一種模式能夠將內容顯示在視圖的中心。在這些模式的做用下,改變邊界矩形只會簡單地將現有的視圖內容移動到新的邊界矩形中對應的位置上。

當您但願在應用程序中實現尺寸可調整的控件時,請務必考慮使用內容模式。這樣作能夠避免控件的外觀發生變形,以及避免編寫定製的描畫代 碼。按鍵和分段控件(segmented control)特別適合基於內容模式的描畫。它們一般使用幾個圖像來建立控件外觀。除了有兩個固定尺寸的蓋帽圖像以外,按鍵能夠經過一個可伸展的、寬度 只有一個像素的中心圖像來實現水平方向的尺寸調整。它將每一個圖像顯示在本身的圖像視圖中,而將可伸展的中間圖像的內容模式設置爲UIViewContentModeScaleToFill,使得在尺寸調整時兩端的外觀不會變形。更爲重要的是,每一個圖像視圖的關聯圖像均可以由Core Animation來緩存,所以不須要編寫描畫代碼就能夠支持動畫,從而使大大提升了性能。

內容模式一般有助於避免視圖內容的描畫,可是當您但願對縮放和尺寸調整過程當中的視圖外觀進行特別的控制時,也可使用UIViewContentModeRedraw模式。將視圖的內容模式設置爲這個值能夠強制Core Animation使視圖的內容失效,並調用視圖的drawRect:方法,而不是自動進行縮放或尺寸調整。

自動尺寸調整行爲

當您改變視圖的邊框矩形時,其內嵌子視圖的位置和尺寸每每也須要改變,以適應原始視圖的新尺寸。若是視圖的autoresizesSubviews屬性聲明被設置爲YES,則其子視圖會根據autoresizingMask屬性的值自動進行尺寸調整。簡單配置一下視圖的自動尺寸調整掩碼經常就能使應用程序獲得合適的行爲;不然,應用程序就必須經過重載layoutSubviews方法來提供本身的實現。

設置視圖的自動尺寸調整行爲的方法是經過位OR操做符將指望的自動尺寸調整常量連結起來,並將結果賦值給視圖的autoresizingMask屬性。表2-1列舉了自動尺寸調整常量,並描述這些常量如何影響給定視圖的尺寸和位置。舉例來講,若是要使一個視圖和其父視圖左下角的相對位置保持不變,能夠加入UIViewAutoresizingFlexibleRightMarginUIViewAutoresizingFlexibleTopMargin常量,並將結果賦值給autoresizingMask屬性。當同一個軸向有多個部分被設置爲可變時,尺寸調整的裕量會被平均分配到各個部分上。

表2-1   自動尺寸調整掩碼常量

自動尺寸調整掩碼

描述

UIViewAutoresizingNone

這個常量若是被設置,視圖將不進行自動尺寸調整。

UIViewAutoresizingFlexibleHeight

這個常量若是被設置,視圖的高度將和父視圖的高度一塊兒成比例變化。不然,視圖的高度將保持不變。

UIViewAutoresizingFlexibleWidth

這個常量若是被設置,視圖的寬度將和父視圖的寬度一塊兒成比例變化。不然,視圖的寬度將保持不變。

UIViewAutoresizingFlexibleLeftMargin

這個常量若是被設置,視圖的左邊界將隨着父視圖寬度的變化而按比例進行調整。不然,視圖和其父視圖的左邊界的相對位置將保持不變。

UIViewAutoresizingFlexibleRightMargin

這個常量若是被設置,視圖的右邊界將隨着父視圖寬度的變化而按比例進行調整。不然,視圖和其父視圖的右邊界的相對位置將保持不變。

UIViewAutoresizingFlexibleBottomMargin

這個常量若是被設置,視圖的底邊界將隨着父視圖高度的變化而按比例進行調整。不然,視圖和其父視圖的底邊界的相對位置將保持不變。

UIViewAutoresizingFlexibleTopMargin

這個常量若是被設置,視圖的上邊界將隨着父視圖高度的變化而按比例進行調整。不然,視圖和其父視圖的上邊界的相對位置將保持不變。

圖2-8爲這些常量值的位置提供了一個圖形表示。若是這些常量之一被省略,則視圖在相應方向上的佈局就被固定;若是某個常量被包含在掩碼中,在該方向的視圖佈局就就靈活的。

圖2-8  視圖的自動尺寸調整掩碼常量

若是您經過Interface Builder配置視圖,則能夠用Size查看器的Autosizing控制來設置每一個視圖的自動尺寸調整行爲。上圖中的靈活寬度及高度常量和 Interface Builder中位於一樣位置的彈簧具備一樣的行爲,可是空白常量的行爲則是正好相反。換句話說,若是要將靈活右空白的自動尺寸調整行爲應用到 Interface Builder的某個視圖,必須使相應方向空間的Autosizing控制爲空,而不是放置一個支柱。幸運的是,Interface Builder經過動畫顯示了您的修改對視圖自動尺寸調整行爲的影響。

若是視圖的autoresizesSubviews屬性被設置爲NO,則該視圖的直接子視圖的全部自動尺寸調整行爲將被忽略。相似地,若是一個子視圖的自動尺寸調整掩碼被設置爲UIViewAutoresizingNone,則該子視圖的尺寸將不會被調整,於是其直接子視圖的尺寸也不會被調整。

請注意:爲了使自動尺寸調整的行爲正確,視圖的transform屬性必須設置爲恆等變換;其它變換下的尺寸自動調整行爲是未定義的。

自動尺寸調整行爲能夠適合一些佈局的要求,可是若是您但願更多地控制視圖的佈局,能夠在適當的視圖類中重載layoutSubviews方法。有關視圖佈局管理的更多信息,請參見「響應佈局的變化」部分。

建立和管理視圖層次

管理用戶界面的視圖層次是開發應用程序用戶界面的關鍵部分。視圖的組織方式不只定義了應用程序的視覺外觀,並且還定義了應用程序如何響 應變化。視圖層次中的父-子關係能夠幫助咱們定義應用程序中負責處理觸摸事件的對象鏈。當用戶旋轉設備時,父-子關係也有助於定義每一個視圖的尺寸和位置是 如何隨着界面方向的變化而變化的。

圖2-9顯示了一個簡單的例子,說明如何經過視圖的分層來建立指望的視覺效果。在Clock程序中,頁籤條和導航條視圖,以及定製視圖混合在一塊兒,實現了整個界面。

圖2-9  Clock程序的視圖層

若是您探究Clock程序中視圖之間的關係,就會發現它們很像「改變視圖的層」部分中顯示的關係,窗口對象是應用程序的頁籤條、導航條、和定製視圖的根視圖。

圖2-10  Clock程序的視圖層次

在iPhone應用程序的開發過程當中,有幾種創建視圖層次的方法,包括基於Interface Builder的可視化方法和經過代碼編程的方法。本文的下面部分將向您介紹如何裝配視圖層次,以及如何在創建視圖層次以後尋找其中的視圖,還有如何在不 同的視圖座標系統之間進行轉換。

建立一個視圖對象

建立視圖對象的最簡單方法是使用Interface Builder進行製做,而後將視圖對象從做成的nib文件載 入內存。在Interface Builder的圖形環境中,您能夠將新的視圖從庫中拖出,而後放到窗口或另外一個視圖中,以快速創建須要的視圖層次。Interface Builder使用的是活的視圖對象,所以,當您用這個圖形環境構建用戶界面時,所看到的就是運行時裝載的外觀,並且不須要爲視圖層次中的每一個視圖編寫單 調乏味的內存分配和初始化代碼。

若是您不喜歡Interface Builder和nib文件,也能夠經過代碼來建立視圖。建立一個新的視圖對象時,須要爲其分配內存,並向該對象發送一個initWithFrame:消息,以對其進行初始化。舉例來講,若是您要建立一個新的UIView類的實例做爲其它視圖的容器,則可使用下面的代碼:

CGRect  viewRect = CGRectMake(0, 0, 100, 100);
UIView* myView = [[UIView alloc] initWithFrame:viewRect];

請注意:雖然全部系統提供的視圖對象都支持initWithFrame:消息,可是其中的一部分可能有本身偏好的初始化方法,您應該使用那些方法。有關定製初始化方法的更多信息,請參見相應的類參考文檔。

您在視圖初始化時指定的邊框矩形表明該視圖相對於將來父視圖的位置和大小。在將視圖顯示於屏幕上以前,您須要將它加入到窗口或其它視圖中。在這個時候,UIKit會根據您指定的邊框矩形將視圖放置到其父視圖的相應位置中。有關如何將視圖添加到視圖層次的信息,請參見「添加和移除子視圖」部分。

添加和移除子視圖

Interface Builder是創建視圖層次的最便利工具,由於它可讓您看到視圖在運行時的外觀。在界面製做完成後,它將視圖對象及其層次關係保存在nib文件中。在 運行時,系統會按照nib文件的內容爲應用程序從新建立那些對象和關係。當一個nib文件被裝載時,系統會自動調用重建視圖層次所須要的UIView方法。

若是您不喜歡經過Interface Builder和nib文件來建立視圖層次,則能夠經過代碼來建立。若是一個視圖必須具備某些子視圖才能工做,則應該在其initWithFrame:方法中進行對其建立,以確保子視圖能夠和視圖一塊兒被顯示和初始化。若是子視圖是應用程序設計的一部分(而不是視圖工做必需的),則應該在視圖的初始化代碼以外進行建立。在iPhone程序中,有兩個地方最經常使用於建立視圖和子視圖,它們是應用程序委託對象的applicationDidFinishLaunching:方法和視圖控制器的loadView方法。

您能夠經過下面的方法來操做視圖層次中的視圖對象:

  • 調用父視圖的addSubview:方法來添加視圖,該方法將一個視圖添加到子視圖列表的最後。

  • 調用父視圖的insertSubview:...方法能夠在父視圖的子視圖列表中間插入視圖。

  • 調用父視圖的bringSubviewToFront:sendSubviewToBack:、或exchangeSubviewAtIndex:withSubviewAtIndex:方法能夠對父視圖的子視圖進行從新排序。使用這些方法比從父視圖中移除子視圖並再次插入要快一些。

  • 調用子視圖(而不是父視圖)的removeFromSuperview方法能夠將子視圖從父視圖中移除。

在添加子視圖時,UIKit會根據子視圖的當前邊框矩形肯定其在父視圖中的初始位置。您能夠隨時經過修改子視圖的frame屬性聲明來改變其位置。缺省狀況下,邊框位於父視圖可視邊界外部的子視圖不會被裁剪。若是您但願激活裁剪功能,必須將父視圖的clipsToBounds屬性設置爲YES

程序清單2-1顯示了一個應用程序委託對象的applicationDidFinishLaunching:方法示例。在這個例子中,應用程序委託在啓動時經過代碼建立所有的用戶界面。界面中包含兩個普通的UIView對象,用於顯示基本顏色。每一個視圖都被嵌入到窗口中,窗口也是UIView 的一個子類,所以能夠做爲父視圖。父視圖會保持它們的子視圖,所以這個方法釋放了新建立的視圖對象,以免重複保持。

程序清單2-1  建立一個帶有視圖的窗口

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    // Create the window object and assign it to the
    // window instance variable of the application delegate.
    window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    window.backgroundColor = [UIColor whiteColor];

    // Create a simple red square
    CGRect redFrame = CGRectMake(10, 10, 100, 100);
    UIView *redView = [[UIView alloc] initWithFrame:redFrame];
    redView.backgroundColor = [UIColor redColor];

    // Create a simple blue square
    CGRect blueFrame = CGRectMake(10, 150, 100, 100);
    UIView *blueView = [[UIView alloc] initWithFrame:blueFrame];
    blueView.backgroundColor = [UIColor blueColor];

    // Add the square views to the window
    [window addSubview:redView];
    [window addSubview:blueView];

    // Once added to the window, release the views to avoid the
    // extra retain count on each of them.
    [redView release];
    [blueView release];

    // Show the window.
    [window makeKeyAndVisible];
}

重要提示:在內存管理方面,能夠將子視圖考慮爲其它的集合對象。特別是當您經過addSubview:方法將一個視圖做爲子視圖插入時,父視圖會對其進行保持操做。反過來,當您經過removeFromSuperview方法將子視圖從父視圖移走時,子視圖會被自動釋放。在將視圖加入視圖層次以後釋放該對象能夠避免多餘的保持操做,從而避免內存泄露。

有關Cocoa內存管理約定的更多信息,請參見Cocoa內存管理編程指南

當您爲某個視圖添加子視圖時,UIKit會向相應的父子視圖發送幾個消息,通知它們當前發生的狀態變化。您能夠在本身的定製視圖中對諸如willMoveToSuperview:willMoveToWindow:willRemoveSubview:didAddSubview:didMoveToSuperview、和didMoveToWindow這樣的方法進行重載,以便在事件發生的先後進行必要的處理,並根據發生的變化更新視圖的狀態信息。

在視圖層次創建以後,您能夠經過視圖的superview屬性來取得其父視圖,或者經過subviews屬性取得視圖的子視圖。您也能夠經過isDescendantOfView:方法來斷定一個視圖是否在其父視圖的視圖層中。一個視圖層次的根視圖沒有父視圖,所以其superview屬性被設置爲nil。對於當前被顯示在屏幕上的視圖,窗口對象一般是整個視圖層次的根視圖。

您能夠經過視圖的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類中包含一個tag屬性。藉助這個屬性,您能夠經過一個整數值來標識一個視圖對象。您能夠經過這個屬性來惟一標識視圖層次中的視圖,以及在運行時進行視圖的檢索(基於tag標識的檢索比您自行遍歷視圖層次要快)。tag屬性的缺省值爲0

您能夠經過UIViewviewWithTag:方法來檢索標識過的視圖。該方法從消息的接收者自身開始,經過深度優先的方法來檢索接收者的子視圖。

在運行時修改視圖

應用程序在接收用戶輸入時,須要經過調整本身的用戶界面來進行響應。應用程序可能從新排列界面上的視圖、刷新屏幕上模型數據已被改變的 視圖、或者裝載一組全新的視圖。在決定使用哪一種技術時,要考慮您的用戶界面,以及您但願實現什麼。可是,如何初始化這些技術對於全部應用程序都是同樣的。 本章的下面部分將描述這些技術,以及如何經過這些技術在運行時更新您的用戶界面。

請注意:若是您須要瞭解UIKit如何在框架內部和您的定製代碼之間轉移事件和消息的背景信息,請在繼續閱讀本文以前查閱「視圖交互模型」部分。

實現視圖動畫

動畫爲用戶界面在不一樣狀態之間的遷移過程提供流暢的視覺效果。在iPhone OS中,動畫被普遍用於視圖的位置調整、尺寸變化、甚至是alpha值的變化(以實現淡入淡出的效果)。動畫支持對於製做易於使用的應用程序是相當重要的,所以,UIKit直接將它集成到UIView類中,以簡化動畫的建立過程。

UIView類定義了幾個內在支持動畫的屬性聲明—也就是說,當這些屬性值發生變化時,視圖爲其變化過程提供內建的動畫支持。雖然執行動畫所須要的工做由UIView類自動完成,但您仍然必須在但願執行動畫時通知視圖。爲此,您須要將改變給定屬性的代碼包裝在一個動畫塊中。

動畫塊從調用UIViewbeginAnimations:context:類方法開始,而以調用commitAnimations類方法做爲結束。在這兩個調用之間,您能夠配置動畫的參數和改變但願實行動畫的屬性值。一旦調用commitAnimations方法,UIKit就會開始執行動畫,即把給定屬性從當前值到新值的變化過程用動畫表現出來。動畫塊能夠被嵌套,可是在最外層的動畫塊提交以前,被嵌套的動畫不會被執行。

表2-2列舉了UIView類中支持動畫的屬性。

表2-2   支持動畫的屬性

屬性

描述

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方法來肯定當前是否激活動畫。

配置動畫的委託

您能夠爲動畫塊分配一個委託,並經過該委託接收動畫開始和結束的消息。當您須要在動畫開始前和結束後當即執行其它任務時,可能就須要這樣作。您能夠經過UIViewsetAnimationDelegate:類方法來設置委託,並經過setAnimationWillStartSelector:setAnimationDidStopSelector:方法來指定接收消息的選擇器方法。消息處理方法的形式以下:

- (void)animationWillStart:(NSString *)animationID context:(void *)context;
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context;

上面兩個方法的animationIDcontext參數和動畫塊開始時傳給beginAnimations:context:方法的參數相同:

  • animationID - 應用程序提供的字符串,用於標識一個動畫塊中的動畫。

  • context - 也是應用程序提供的對象,用於向委託對象傳遞額外的信息。

setAnimationDidStopSelector:選擇器方法還有一個參數—即一個布爾值。若是動畫順利完成,沒有被其它動畫取消或中止,則該值爲YES

響應佈局的變化

任什麼時候候,當視圖的佈局發生改變時,UIKit會激活每一個視圖的自動尺寸調整行爲,而後調用各自的layoutSubviews方法,使您有機會進一步調整子視圖的幾何尺寸。下面列舉的情形都會引發視圖佈局的變化:

  • 視圖邊界矩形的尺寸發生變化。

  • 滾動視圖的內容偏移量—也就是可視內容區域的原點—發生變化。

  • 和視圖關聯的轉換矩陣發生變化。

  • 和視圖層相關聯的Core Animation子層組發生變化。

  • 您的應用程序調用視圖的setNeedsLayoutlayoutIfNeeded方法來強制進行佈局。

  • 您的應用程序調用視圖背後的層對象的setNeedsLayout方法來強制進行佈局。

子視圖的初始佈局由視圖的自動尺寸調整行爲來負責。應用這些行爲能夠保證您的視圖接近其設計的尺寸。有關自動尺寸調整行爲如何影響視圖的尺寸和位置的更多信息,請參見「自動尺寸調整行爲」部分。

有些時候,您可能但願經過layoutSubviews方法來手工調整子視圖的佈局,而不是徹底依賴自動尺 寸調整行爲。舉例來講,若是您要實現一個由幾個子視圖元素組成的定製控件,則能夠經過手工調整子視圖來精確控制控件在必定尺寸範圍內的外觀。還有,若是一 個視圖表示的滾動內容區域很大,能夠選擇將內容顯示爲一組平鋪的子視圖,在滾動過程當中,能夠回收離開屏幕邊界的視圖,並在填充新內容後將它從新定位,使它 成爲下一個滾入屏幕的視圖。

請注意:您也能夠用layoutSubviews方法來調整做爲子層連接到視圖層的定製CALayer對象。您能夠經過對隱藏在視圖後面的層層次進行管理,實現直接基於Core Animation的高級動畫。有關如何經過Core Animation管理層層次的更多信息,請參見Core Animation編程指南

在編寫佈局代碼時,請務必在應用程序支持的每一個方向上都進行測試。對於同時支持景觀方向和肖像方向的應用程序,必須確認其是否能正確處 理兩個方向上的佈局。相似地,您的應用程序應該作好處理其它系統變化的準備,好比狀態條高度的變化,若是用戶在使用您的應用程序的同時接聽電話,而後再掛 斷,就會發生這種變化。在掛斷時,負責管理視圖的視圖控制器可能會調整視圖的尺寸,以適應縮小的狀態條。以後,這樣的變化會向下滲透到應用程序的其它視圖。

重畫視圖的內容

有些時候,應用程序數據模型的變化會影響到相應的用戶界面。爲了反映這些變化,您能夠將相應的視圖標識爲須要刷新(經過調用setNeedsDisplaysetNeedsDisplayInRect:方法)。和簡單建立一個圖形上下文並進行描畫相比,將視圖標識爲須要刷新的方法使系統有機會更有效地執行描畫操做。舉例來講,若是您在某個運行週期中將一個視圖的幾個區域標識爲須要刷新,系統就會將這些須要刷新的區域進行合併,並最終造成一個drawRect:方法的調用。結果,只須要建立一個圖形上下文就能夠描畫全部這些受影響的區域。這個作法比連續快速建立幾個圖形上下文要有效得多。

實現drawRect:方法的視圖老是須要檢查傳入的矩形參數,並用它來限制描畫操做的範圍。由於描畫是開銷相對昂貴的操做,以這種方式來限制描畫是提升性能的好方法。

缺省狀況下,視圖在幾何上的變化並不自動致使重畫。相反,大多數幾何變化都由Core Animation來自動處理。具體來講,當您改變視圖的frameboundscenter、或transform屬 性時,Core Animation會將相應的幾何變化應用到與視圖層相關聯的緩存位圖上。在不少狀況下,這種方法是徹底能夠接受的,可是若是您發現結果不是您指望獲得 的,則能夠強制UIKit對視圖進行重畫。爲了不Core Animation自動處理幾何變化,您能夠將視圖的contentMode屬性聲明設置爲UIViewContentModeRedraw。更多有關內容模式的信息,請參見「內容模式和比例縮放」部分。

隱藏視圖

您能夠經過改變視圖的hidden屬性聲明來隱藏或顯示視圖。將這個屬性設置爲YES會隱藏視圖,設置爲NO則能夠顯示視圖。對一個視圖進行隱藏會同時隱藏其內嵌的全部子視圖,就好象它們本身的hidden屬性也被設置同樣。

當您隱藏一個視圖時,該視圖仍然會保留在視圖層次中,但其內容不會被描畫,也不會接收任何觸摸事件。因爲隱藏視圖仍然存在於視圖層次 中,因此會繼續參與自動尺寸調整和其它佈局操做。若是被隱藏的視圖是當前的第一響應者,則該視圖會自動放棄其自動響應者的狀態,但目標爲第一響應者的事件 仍然會傳遞給隱藏視圖。有關響應者鏈的更多信息,請參見「響應者對象和響應者鏈」部分。

建立一個定製視圖

UIView類爲在屏幕上顯示內容及處理觸摸事件提供了潛在的支持,可是除了在視圖區域內描畫帶有alpha值的背景色以外,UIView類的實例不作其它描畫操做,包括其子視圖的描畫。若是您的應用程序須要顯示定製的內容,或以特定的方式處理觸摸事件,必須建立UIView的定製子類。

本章的下面部分將描述一些定製視圖對象可能須要實現的關鍵方法和行爲。有關子類化的更多信息,請參見UIView類參考

初始化您的定製視圖

您定義的每一個新的視圖對象都應該包含initWithFrame:初始化方法。該方法負責在建立對象時對類進行初始化,使之處於已知的狀態。在經過代碼建立您的視圖實例時,須要使用這個方法。

程序清單2-2顯示了標準的initWithFrame:方法的一個框架實現。該實現首先調用繼承自超類的實現,而後初始化類的實例變量和狀態信息,最後返回初始化完成的對象。您一般須要首先執行超類的實現,以便在出現問題時能夠簡單地終止本身的初始化代碼,返回nil

程序清單2-2  初始化一個視圖的子類

- (id)initWithFrame:(CGRect)aRect {
    self = [super initWithFrame:aRect];
    if (self) {
          // setup the initial properties of the view
          ...
       }
    return self;
}

若是您從nib文件中裝載定製視圖類的實例,則須要知道:在iPhone OS中,裝載nib的代碼並不經過initWithFrame:方法來實例化新的視圖對象,而是經過NSCoding協議定義的initWithCoder:方法來進行。

即便您的視圖採納了NSCoding協議,Interface Builder也不知道它的定製屬性,所以不知道如何將那些屬性編碼到nib文件中。因此,當您從nib文件裝載定製視圖時,initWithCoder:方法不具備進行正確初始化所須要的信息。爲了解決這個問題,您能夠在本身的類中實現awakeFromNib方法,特別用於從nib文件裝載的定製類。

描畫您的視圖內容

當您改變視圖內容時,能夠經過setNeedsDisplaysetNeedsDisplayInRect:方法來將須要重畫的部分通知給系統。在應用程序返回運行循環以後,會對全部的描畫請求進行合併,計算界面中須要被更新的部分;以後就開始遍歷視圖層次,向須要更新的視圖發送drawRect:消息。遍歷的起點是視圖層次的根視圖,而後從後往前遍歷其子視圖。在可視邊界內顯示定製內容的視圖必須實現其drawRect:方法,以便對該內容進行渲染。

在調用視圖的drawRect:方法以前,UIKit會爲其配置描畫的環境,即建立一個圖形上下文,並調整其座標系統和裁剪區,使之和視圖的座標系統及邊界相匹配。所以,在您的drawRect:方法被調用時,您可使用UIKit的類和函數、Quartz的函數、或者使用二者相結合的方法來直接進行描畫。須要的話,您能夠經過UIGraphicsGetCurrentContext函數來取得當前圖形上下文的指針,實現對它的訪問。

重要提示:只有當定製視圖的drawRect:方法被調用的期間,當前圖形上下文才是有效的。UIKit可能爲該方法的每一個調用建立不一樣的圖形上下文,所以,您不該該對該對象進行緩存並在以後使用。

程序清單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對象的beginIgnoringInteractionEventsendIgnoringInteractionEvents方法。這些方法影響的是整個應用程序的事件分發,而不只僅是某個視圖。

在處理觸摸事件時,UIKit會經過UIViewhitTest: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];
}
相關文章
相關標籤/搜索