文檔地址: 《View Programming Guide for iOS》
View and Window Architecture
-
視圖繪制周期
UIView 類使用了請求式繪制模型來展示內(nèi)容。當一個視圖第一次出現(xiàn)在屏幕上時,系統(tǒng)要求它繪制自己的內(nèi)容。系統(tǒng)截取視圖內(nèi)容的一個快照,并且將這個快照用于視圖的可視化呈現(xiàn)。如果視圖內(nèi)容永遠不改變,那么這個視圖的繪圖代碼可能永遠都不會再次調(diào)用。這個快照的圖片在大部分涉及到該視圖的操作中被重復(fù)使用。如果改變了視圖內(nèi)容,則需要通知系統(tǒng)視圖發(fā)生了改變。之后視圖會重復(fù)繪制過程并且為新的繪制結(jié)果截取一個快照。
當視圖內(nèi)容發(fā)生改變時,不需要直接重繪這些改變。相反,通過調(diào)用函數(shù) setNeedsDisplay 或者 setNeedsDisplayInRect: 來使當前視圖無效。這些函數(shù)會告訴系統(tǒng)視圖的內(nèi)容發(fā)生改變并且需要在下次時機到來時重繪。系統(tǒng)會一直等到當前的 run loop 結(jié)束后,才會開始任何繪制操作。這個延遲,給了你一個機會去廢止多個視圖,從當前視圖層級中添加或者刪除視圖,隱藏視圖,重設(shè)視圖大小,和重定位視圖。所有的這些改變稍后會再同一時間呈現(xiàn)。
備注:改變視圖的幾何結(jié)構(gòu)并不會讓系統(tǒng)自動重繪視圖內(nèi)容。視圖的 contentMode 屬性決定了視圖幾何結(jié)構(gòu)的改變該如何解析。大部分的 content modes 只是在視圖的邊界中拉伸或者重定位已經(jīng)存在的視圖快照而不需要重新創(chuàng)建一個快照。
當繪制視圖內(nèi)容的時刻到來時,真正的繪制過程會根據(jù)視圖和它的配置而有所不同。系統(tǒng)視圖通常是實現(xiàn)自己的私有繪圖函數(shù)來重繪內(nèi)容。這些一樣的系統(tǒng)視圖通常會暴露一些接口,以便能用來配置視圖實際的外觀。對于自定義的 UIView 的子類,典型的應(yīng)該重寫視圖的 drawRect: 函數(shù),使用它來繪制視圖內(nèi)容。當然也存在一些其他的方法去提供視圖的內(nèi)容,比如直接設(shè)置內(nèi)容下的圖層。但是重寫 drawRect: 函數(shù)是使用最多的技術(shù)。
UIKit 框架的坐標原點位于左上角,x 軸向右延伸,y 軸向下延伸。而 Core Graphics 和 OpenGL ES 的坐標系統(tǒng)原點則在左下角,y 軸向上延伸,x 軸向右延伸。
UIView 屬性中的 frame和 center 是相對于父視圖的坐標系統(tǒng)的。而 bounds屬性相對于自身的坐標系統(tǒng),故 bounds 默認的 point 位置是(0,0),大小與 frame 相同
改變視圖的 transform 屬性時,所有變形都是相對于視圖中心點也就是 center 屬性的。
-
在視圖的 drawRect: 方法中,可以使用仿射變換來定位和確定需要繪制的元素。相比于在視圖的某個地點固定一個對象的位置,相對于一個固定點(通常是(0,0))來創(chuàng)建每個對象是更為簡單的。在繪制之前使用 transform 就能做到這點。在這種情況下,如果視圖中的對象位置發(fā)生改變,只需要修改這個 transform 即可,這比在新的位置重新創(chuàng)建對象要快速并且花銷更小??梢允褂?CGContextGetCTM 函數(shù)來檢索圖形上下文的仿射變換矩陣,在繪制過程中也可以使用 Core Graphics 的相關(guān)函數(shù)來設(shè)置 CTM。
CTM(current transformation matrix) 是任何時候都被使用的仿射變換,當操作的是整個視圖時,CTM 就是視圖的 transform 屬性。在 drawRect: 方法中, CTM 與當前活動的圖形上下文有關(guān)
當一個視圖的 transform 屬性不是 identity transform 時,這個視圖的 frame 屬性就是未定義并且必須被忽視的。此時,你必須使用視圖的 bounds 和 center 屬性來獲得視圖的大小和位置。該視圖的任何子視圖的 frame 矩形依然是有效的,因為它們是基于父視圖的 bounds 屬性的。
一個點并不一定對應(yīng)著屏幕上的一個像素
對于顯式定義了 drawRect: 方法的視圖來說,UIKit 負責調(diào)用這個方法。這個方法中的實現(xiàn)應(yīng)該盡可能快地重繪視圖的指定區(qū)域并且不應(yīng)該做別的任何事情。不要在這里做額外的布局,也不要改變應(yīng)用的數(shù)據(jù)模型。這個方法的唯一目的就是更新視圖的可視內(nèi)容。
自定義視圖需要重寫的事件處理函數(shù)有
touchesBegan:withEvent:,touchesMoved:withEvent:,touchesEnded:withEvent:,touchesCancelled:withEvent:如果使用了手勢識別來處理事件,則不需要重寫這些函數(shù)。如果視圖不包含任何子視圖或者它的尺寸不發(fā)生改變,也不需要重寫layoutSubviews函數(shù)。最后,當視圖內(nèi)容在運行時發(fā)生改變,同時使用了 UIKit 或者 Core Graphics 來繪制圖形,則需要重寫drawRect:函數(shù)。
Windows
每個 iOS 應(yīng)用程序至少包含一個窗口。窗口通常座位一個或者多個視圖的空白容器。同時,應(yīng)用程序也不通過展示新的窗口來改變內(nèi)容。如果想要這么做,改變窗口最前面的視圖來完成。
當創(chuàng)建窗口時,應(yīng)該總是將窗口的大小設(shè)置為充滿屏幕的邊界。不應(yīng)該為了容納狀態(tài)欄或者其他元素而減去窗口大小。無論何時,狀態(tài)欄總是浮在窗口的上面的。所以應(yīng)該是放入到窗口中的視圖來縮減大小去適應(yīng)狀態(tài)欄。如果是使用視圖控制器,則視圖控制器應(yīng)該自動處理視圖大小。
窗口有等級概念,每個
UIWindow對象都有一個可配置的windowLevel屬性。通常不需要改變應(yīng)用程序的窗口等級。新的窗口在創(chuàng)建時,會自動指派為正常窗口等級。高窗口等級是出現(xiàn)在應(yīng)用程序內(nèi)容之上的必要信息,比如系統(tǒng)狀態(tài)欄和 alert 消息。雖然可以手動將窗口設(shè)置為這樣的等級,但當使用到特殊接口時,通常系統(tǒng)會做好這些事情。舉例來說,當顯示隱藏狀態(tài)欄,或者顯示一個 alert 視圖時,系統(tǒng)會自動創(chuàng)建必要的窗口去顯示這些內(nèi)容。當應(yīng)用程序進入到后臺時,窗口改變通知并不會被傳遞。因為當程序進入后臺時盡管窗口不在屏幕上顯示了,但在應(yīng)用程序環(huán)境中,窗口依然被認為是可見的。
retina 屏的 iOS 設(shè)備可以外接顯示設(shè)備。
Views
- 使用編程方法來創(chuàng)建視圖時,視圖創(chuàng)建代碼一般放在視圖控制器的
loadView函數(shù)中。無論是使用編程或者 nib 文件來創(chuàng)建視圖,都可以在viewDidLoad函數(shù)中添加視圖的配置代碼。 - 父視圖會自動 retain 子視圖,所以當添加了一個子視圖后,release 子視圖的操作是安全的。事實上,推薦這么做,因為它能防止應(yīng)用程序保持太多的視圖而導(dǎo)致的內(nèi)存泄露。記住,如果從父視圖中移除了子視圖后,還想繼續(xù)使用子視圖,必須對子視圖做 retain 操作。
removeFromSuperview函數(shù)會在子視圖從父視圖中移除后,自動釋放子視圖。如果沒有在下一個時間循環(huán)周期前做 retain 操作,這個視圖將會被釋放。 - UIView的
window屬性代表當前正在顯示的視圖所在的窗口。對于當前在屏幕上顯示的視圖來說,窗口對象就是它們所在視圖層次的根視圖。 - 如果隱藏的視圖是 first responder,這個視圖不會自動的取消自己 first responder 的狀態(tài)。以 first responder 為目標的事件依然會被傳遞到這個隱藏的視圖。為了防止這種情況發(fā)生,應(yīng)該在隱藏視圖時,強制使其取消 first responder 狀態(tài)。
- 當包含了旋轉(zhuǎn)因子的視圖做矩形轉(zhuǎn)換時,看如下的圖:

如果一個視圖的
transform屬性不是 identity transform,那么它的 frame 和 autoresizing 行為結(jié)果都是未定義的。視圖在初始化過程之前調(diào)用 UIView 類方法
layerClass,并且使用返回的類來創(chuàng)建 layer 對象。此外,視圖總是會指定自己本身作為 layer 對象的代理。在這點上,視圖擁有著圖層,并且視圖和圖層之前的關(guān)系必須不能改變。也就是說,不能指定相同的視圖作為另一個圖層對象的代理。改變這種所屬關(guān)系或者代理關(guān)系,都可能會導(dǎo)致視圖繪制的錯誤,并且應(yīng)用程序存在潛在的 crash 問題。通過創(chuàng)建 UIView 的子類,重寫
layerClass類函數(shù)可以改變創(chuàng)建圖層時的默認的 CALayer 類。自定義的 layer 不接收事件,也不參與到 responder chain 中,但是它們繪制自身,并且根據(jù) Core Animation 的規(guī)則,在他們的父視圖或者父圖層中響應(yīng)大小變化。
CGRectGetMidX(),CGRectGetMidY() 兩個函數(shù)可以分別得到一個 frame 的中心點的 x 坐標和 y 坐標。
CALayer 的屬性
position就是中心點,和 UIView 的屬性center效果相同。自定義的視圖類,如果是通過代碼來創(chuàng)建,則需要重寫
initWithFrame:初始化函數(shù)。而若是從 nib 文件中加載,則需要重寫initWithCoder:函數(shù)。注意:nib 加載的視圖并不會調(diào)用initWithFrame:函數(shù)。視圖默認的行為是一次只響應(yīng)一個 touch。如果用戶按下了第二個手指,系統(tǒng)會忽視這個 touch 事件并且不會將它報告給視圖。如果希望在視圖的事件處理函數(shù)中跟蹤多手指手勢,需要設(shè)置視圖的
multipleTouchEnabled屬性為 YES 來使多點觸摸事件生效。
轉(zhuǎn)自:WebFrogs 的博客