CALayer的屬性行為其實(shí)很不正常,因?yàn)楦淖円粋€(gè)圖層的屬性并沒有立刻生效,而是通過一段時(shí)間漸變更新。這是怎么做到的呢?
當(dāng)你改變一個(gè)圖層的屬性,屬性值的確是立刻更新的(如果你讀取它的數(shù)據(jù),你會發(fā)現(xiàn)它的值在你設(shè)置它的那一刻就已經(jīng)生效了),但是屏幕上并沒有馬上發(fā)生改變。這是因?yàn)槟阍O(shè)置的屬性并沒有直接調(diào)整圖層的外觀,相反,他只是定義了圖層動畫結(jié)束之后將要變化的外觀。
當(dāng)設(shè)置CALayer的屬性,實(shí)際上是在定義當(dāng)前事務(wù)結(jié)束之后圖層如何顯示的模型。Core Animation扮演了一個(gè)控制器的角色,并且負(fù)責(zé)根據(jù)圖層行為和事務(wù)設(shè)置去不斷更新視圖的這些屬性在屏幕上的狀態(tài)。
我們討論的就是一個(gè)典型的微型MVC模式。CALayer是一個(gè)連接用戶界面(就是MVC中的view)虛構(gòu)的類,但是在界面本身這個(gè)場景下,CALayer的行為更像是存儲了視圖如何顯示和動畫的數(shù)據(jù)模型。實(shí)際上,在蘋果自己的文檔中,圖層樹通常都是值的圖層樹模型。
在蘋果手機(jī)屏幕中,屏幕每秒鐘重繪60次。如果動畫時(shí)長比60分之一秒要長,Core Animation就需要在設(shè)置一次新值和新值生效之間,對屏幕上的圖層進(jìn)行重新組織。這意味著CALayer除了“真實(shí)”值(就是你設(shè)置的值)之外,必須要知道當(dāng)前顯示在屏幕上的屬性值的記錄。
每個(gè)圖層屬性的顯示值都被存儲在一個(gè)叫做呈現(xiàn)圖層的獨(dú)立圖層當(dāng)中,他可以通過-presentation()方法來訪問。這個(gè)呈現(xiàn)圖層實(shí)際上是模型圖層的復(fù)制,但是它的屬性值代表了在任何指定時(shí)刻當(dāng)前外觀效果。換句話說,你可以通過呈現(xiàn)圖層的值來獲取當(dāng)前屏幕上真正顯示出來的值。
在iOS中除了圖層樹,另外還有呈現(xiàn)樹。呈現(xiàn)樹通過圖層樹中所有圖層的呈現(xiàn)圖層所形成。注意呈現(xiàn)圖層僅僅當(dāng)圖層首次被提交(就是首次第一次在屏幕上顯示)的時(shí)候創(chuàng)建,所以在那之前調(diào)用-presentation()將會返回nil。
你可能注意到有一個(gè)叫做–model()的方法。在呈現(xiàn)圖層上調(diào)用–model()將會返回它正在呈現(xiàn)所依賴的CALayer。通常在一個(gè)圖層上調(diào)用-model()會返回–self(實(shí)際上我們已經(jīng)創(chuàng)建的原始圖層就是一種數(shù)據(jù)模型)。
大多數(shù)情況下,你不需要直接訪問呈現(xiàn)圖層,你可以通過和模型圖層的交互,來讓Core Animation更新顯示。兩種情況下呈現(xiàn)圖層會變得很有用,一個(gè)是同步動畫,一個(gè)是處理用戶交互。
如果你在實(shí)現(xiàn)一個(gè)基于定時(shí)器的動畫,而不僅僅是基于事務(wù)的動畫,這個(gè)時(shí)候準(zhǔn)確地知道在某一時(shí)刻圖層顯示在什么位置就會對正確擺放圖層很有用了。
如果你想讓你做動畫的圖層響應(yīng)用戶輸入,你可以使用-hitTest:方法來判斷指定圖層是否被觸摸,這時(shí)候?qū)Τ尸F(xiàn)圖層而不是模型圖層調(diào)用-hitTest:會顯得更有意義,因?yàn)槌尸F(xiàn)圖層代表了用戶當(dāng)前看到的圖層位置,而不是當(dāng)前動畫結(jié)束之后的位置。
我們可以用一個(gè)簡單的案例來證明后者。在這個(gè)例子中,點(diǎn)擊屏幕上的任意位置將會讓圖層平移到那里。點(diǎn)擊圖層本身可以隨機(jī)改變它的顏色。我們通過對呈現(xiàn)圖層調(diào)用-hitTest:來判斷是否被點(diǎn)擊。
如果修改代碼讓-hitTest:直接作用于colorLayer而不是呈現(xiàn)圖層,你會發(fā)現(xiàn)當(dāng)圖層移動的時(shí)候它并不能正確顯示。這時(shí)候你就需要點(diǎn)擊圖層將要移動到的位置而不是圖層本身來響應(yīng)點(diǎn)擊(這就是為什么用呈現(xiàn)圖層來響應(yīng)交互的原因)。
使用presentationLayer圖層來判斷當(dāng)前圖層位置的代碼:
<pre>import UIKit
class ViewController: UIViewController {
var colorLayer: CALayer!
override func viewDidLoad() {
super.viewDidLoad()
self.initView()
}
func initView()
{
self.colorLayer = CALayer()
self.colorLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
self.colorLayer.position = CGPoint(x: self.view.bounds.size.width / 2, y: self.view.bounds.size.height / 2)
self.colorLayer.backgroundColor = UIColor.blue.cgColor
self.view.layer.addSublayer(self.colorLayer)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
let touch = touches.first
let point = touch?.location(in: self.view)
if ((self.colorLayer.presentation()?.hitTest(point!)) != nil)
{
let red = arc4random() % 256
let green = arc4random() % 256
let blue = arc4random() % 256
self.colorLayer.backgroundColor = UIColor(red: CGFloat(red) / 255, green: CGFloat(green) / 255, blue: CGFloat(blue) / 255, alpha: 1).cgColor
}else
{
CATransaction.begin()
CATransaction.setAnimationDuration(4)
self.colorLayer.position = point!
CATransaction.commit()
}
}
}
</pre>