Swift 隐式动画中的呈现与模型

CALayer的属性行为其实很不正常,因为改变一个图层的属性并没有立刻生效,而是通过一段时间渐变更新。这是怎么做到的呢?
当你改变一个图层的属性,属性值的确是立刻更新的(如果你读取它的数据,你会发现它的值在你设置它的那一刻就已经生效了),但是屏幕上并没有马上发生改变。这是因为你设置的属性并没有直接调整图层的外观,相反,他只是定义了图层动画结束之后将要变化的外观。
当设置CALayer的属性,实际上是在定义当前事务结束之后图层如何显示的模型。Core Animation扮演了一个控制器的角色,并且负责根据图层行为和事务设置去不断更新视图的这些属性在屏幕上的状态。
我们讨论的就是一个典型的微型MVC模式。CALayer是一个连接用户界面(就是MVC中的view)虚构的类,但是在界面本身这个场景下,CALayer的行为更像是存储了视图如何显示和动画的数据模型。实际上,在苹果自己的文档中,图层树通常都是值的图层树模型。
在苹果手机屏幕中,屏幕每秒钟重绘60次。如果动画时长比60分之一秒要长,Core Animation就需要在设置一次新值和新值生效之间,对屏幕上的图层进行重新组织。这意味着CALayer除了“真实”值(就是你设置的值)之外,必须要知道当前显示在屏幕上的属性值的记录。
每个图层属性的显示值都被存储在一个叫做呈现图层的独立图层当中,他可以通过-presentation()方法来访问。这个呈现图层实际上是模型图层的复制,但是它的属性值代表了在任何指定时刻当前外观效果。换句话说,你可以通过呈现图层的值来获取当前屏幕上真正显示出来的值。
在iOS中除了图层树,另外还有呈现树。呈现树通过图层树中所有图层的呈现图层所形成。注意呈现图层仅仅当图层首次被提交(就是首次第一次在屏幕上显示)的时候创建,所以在那之前调用-presentation()将会返回nil。
你可能注意到有一个叫做–model()的方法。在呈现图层上调用–model()将会返回它正在呈现所依赖的CALayer。通常在一个图层上调用-model()会返回–self(实际上我们已经创建的原始图层就是一种数据模型)。

移动的图层是如何通过数据模型呈现的.png

大多数情况下,你不需要直接访问呈现图层,你可以通过和模型图层的交互,来让Core Animation更新显示。两种情况下呈现图层会变得很有用,一个是同步动画,一个是处理用户交互。
如果你在实现一个基于定时器的动画,而不仅仅是基于事务的动画,这个时候准确地知道在某一时刻图层显示在什么位置就会对正确摆放图层很有用了。
如果你想让你做动画的图层响应用户输入,你可以使用-hitTest:方法来判断指定图层是否被触摸,这时候对呈现图层而不是模型图层调用-hitTest:会显得更有意义,因为呈现图层代表了用户当前看到的图层位置,而不是当前动画结束之后的位置。
我们可以用一个简单的案例来证明后者。在这个例子中,点击屏幕上的任意位置将会让图层平移到那里。点击图层本身可以随机改变它的颜色。我们通过对呈现图层调用-hitTest:来判断是否被点击。
如果修改代码让-hitTest:直接作用于colorLayer而不是呈现图层,你会发现当图层移动的时候它并不能正确显示。这时候你就需要点击图层将要移动到的位置而不是图层本身来响应点击(这就是为什么用呈现图层来响应交互的原因)。

使用presentationLayer图层来判断当前图层位置的代码:

<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>

效果图.gif

推荐阅读更多精彩内容

  • 书写的很好,翻译的也棒!感谢译者,感谢感谢! iOS-Core-Animation-Advanced-Techni...
    钱嘘嘘阅读 1,731评论 0 6
  • Core Animation其实是一个令人误解的命名。你可能认为它只是用来做动画的,但实际上它是从一个叫做Laye...
    小猫仔阅读 2,581评论 1 4
  • 图层树 在UIKit中所有的视图都是基于UIView派生而来,UIView支持触摸时间,可以支持基于CoreGra...
    maguns阅读 926评论 1 3
  • 前言 本文只要描述了iOS中的Core Animation(核心动画:隐式动画、显示动画)、贝塞尔曲线、UIVie...
    GitHubPorter阅读 3,028评论 7 11
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 4,071评论 5 10
  • 头文件 .h : 实现文件 .m :
    木马不在转阅读 503评论 2 3
  • 如果生活没有阳光,只有雾霾 我们将会怎样? 也许你知道 也许他知道 但我不知道 曾几何时 雾霾的荣光骚进了我的眼 ...
    暮晓海阅读 224评论 0 0
  • 三月的珠海,温暖湿润,带着独特的南方气息,让人感觉到些许的沉闷与压抑。可偏偏在这时节,有一抹红色格外靓丽、格外绚...
    怿栀阅读 636评论 0 2
  • 望着桌子上镀金的请柬,我颤抖着摸出一支烟,缓缓点上,深吸一口,吐出的烟雾就如同我此刻的心情,飘摇不定。 ...
    一场风花雪月的事阅读 224评论 1 1
  • 终于,我坐上了飞机,这趟飞机是由萧山机场飞往深圳,转道香港。你可能会问,我到香港去干什么?我告诉你,不是去旅游,而...
    小王子子鉴阅读 209评论 1 5