1. 程式人生 > >詳解 CALayer 和 UIView 的區別和聯絡

詳解 CALayer 和 UIView 的區別和聯絡

前面發了一篇iOS 面試的文章,在說到 UIView 和 CALayer 的區別和聯絡的時候,被喵神指出沒有切中要點,所以這裡就 CALayer 和 UIView 這個問題重新整理了下。這裡會先分條解釋,最後會在文章的結尾給出概括性總結。

1.首先UIView可以響應事件,Layer不可以.

UIKit使用UIResponder作為響應物件,來響應系統傳遞過來的事件並進行處理。UIApplication、UIViewController、UIView、和所有從UIView派生出來的UIKit類(包括UIWindow)都直接或間接地繼承自UIResponder類。

在 UIResponder中定義了處理各種事件和事件傳遞的介面, 而 CALayer直接繼承 NSObject,並沒有相應的處理事件的介面。

下面列舉一些處理觸控事件的介面

  • – touchesBegan:withEvent:

  • – touchesMoved:withEvent:

  • – touchesEnded:withEvent:

  • – touchesCancelled:withEvent:

其實還有一些運動和遠端控制事件等等,這裡就不一一列舉了。

下面的兩篇文章詳細介紹了 iOS 事件的處理和傳遞

參考連結:

2.View和CALayer的Frame對映及View如何建立CALayer.

一個 Layer 的 frame 是由它的 anchorPoint,position,bounds,和 transform 共同決定的,而一個 View 的 frame 只是簡單的返回 Layer的 frame,同樣 View 的 center和 bounds 也是返回 Layer 的一些屬性。(PS:center有些特列)為了證明這些,我做了如下的測試。

首先我自定義了兩個類CustomView,CustomLayer分別繼承 UIView 和 CALayer

在 CustomView 中重寫了

+ (Class)layerClass
{
    return [CustomLayer class];
}
- (void)setFrame:(CGRect)frame
{
    [super setFrame:frame];
}
- (void)setCenter:(CGPoint)center
{
    [super setCenter:center];
}
- (void)setBounds:(CGRect)bounds
{
    [super setBounds:bounds];
}

同樣在 CustomLayer中同樣重寫這些方法。只是 setCenter方法改成setPosition方法

我在兩個類的初始化方法中都打下了斷點

blob.png

首先我們會發現,我們在 [view initWithFrame] 的時候呼叫私有方法【UIView _createLayerWithFrame】去建立 CALayer。

然後我在建立 View 的時候,在 Layer 和 View 中Frame 相關的所有方法中都加上斷點,可以看到大致如下的呼叫順序如下

[UIView _createLayerWithFrame]
[Layer setBounds:bounds]
[UIView setFrame:Frame]
[Layer setFrame:frame]
[Layer setPosition:position]
[Layer setBounds:bounds]

我發現在建立的過程只有呼叫了 Layer 的設定尺寸和位置的然而並沒有呼叫View 的 SetCenter 和 SetBounds 方法。

然後我發現當我修改了 view的 bounds.size 或者 bounds.origin 的時候也只會呼叫上邊 Layer的一些方法。所以我大膽的猜一下,View 的 Center 和 Bounds 只是直接返回layer 對應的 Position 和 Bounds.

View中frame getter方法,bounds和center,UIView並沒有做什麼工作;它只是簡單的各自呼叫它底層的CALayer的frame,bounds和position方法。

3.UIView主要是對顯示內容的管理而 CALayer 主要側重顯示內容的繪製。

我在 UIView 和 CALayer 分別重寫了父類的方法。

[UIView drawRect:rect]//UIView    
[CALayer display]//CALayer

然後我在上面兩個方法加了斷點,可以看到如下的執行。

blob.png

可以看到 UIView 是 CALayer 的CALayerDelegate,我猜測是在代理方法內部[UIView(CALayerDelegate) drawLayer:inContext]呼叫 UIView 的 DrawRect方法,從而繪製出了 UIView 的內容.

4.在做 iOS 動畫的時候,修改非 RootLayer的屬性(譬如位置、背景色等)會預設產生隱式動畫,而修改UIView則不會。

對於每一個 UIView 都有一個 layer,把這個 layer 且稱作RootLayer,而不是 View 的根 Layer的叫做 非 RootLayer。我們對UIView的屬性修改時時不會產生預設動畫,而對單獨 layer屬性直接修改會,這個預設動畫的時間預設值是0.25s.

在 Core Animation 程式設計指南的 “How to Animate Layer-Backed Views” 中,對為什麼會這樣做出了一個解釋:

UIView 預設情況下禁止了 layer 動畫,但是在 animation block 中又重新啟用了它們

是因為任何可動畫的 layer 屬性改變時,layer 都會尋找並執行合適的 'action' 來實行這個改變。在 Core Animation 的專業術語中就把這樣的動畫統稱為動作 (action,或者 CAAction)。

layer 通過向它的 delegate 傳送 actionForLayer:forKey: 訊息來詢問提供一個對應屬性變化的 action。delegate 可以通過返回以下三者之一來進行響應:

  1. 它可以返回一個動作物件,這種情況下 layer 將使用這個動作。

  2. 它可以返回一個 nil, 這樣 layer 就會到其他地方繼續尋找。

  3. 它可以返回一個 NSNull 物件,告訴 layer 這裡不需要執行一個動作,搜尋也會就此停止。

當 layer 在背後支援一個 view 的時候,view 就是它的 delegate;

總結

總接來說就是如下幾點:

  • 每個 UIView 內部都有一個 CALayer 在背後提供內容的繪製和顯示,並且 UIView 的尺寸樣式都由內部的 Layer 所提供。兩者都有樹狀層級結構,layer 內部有 SubLayers,View 內部有 SubViews.但是 Layer 比 View 多了個AnchorPoint

  • 在 View顯示的時候,UIView 做為 Layer 的 CALayerDelegate,View 的顯示內容由內部的 CALayer 的 display

  • CALayer 是預設修改屬性支援隱式動畫的,在給 UIView 的 Layer 做動畫的時候,View 作為 Layer 的代理,Layer 通過 actionForLayer:forKey:向 View請求相應的 action(動畫行為)

  • layer 內部維護著三分 layer tree,分別是 presentLayer Tree(動畫樹),modeLayer Tree(模型樹), Render Tree (渲染樹),在做 iOS動畫的時候,我們修改動畫的屬性,在動畫的其實是 Layer 的 presentLayer的屬性值,而最終展示在介面上的其實是提供 View的modelLayer

  • 兩者最明顯的區別是 View可以接受並處理事件,而 Layer 不可以

參考連結