使用 Swift3 子类化 CALayer + 隐式动画

Posted

技术标签:

【中文标题】使用 Swift3 子类化 CALayer + 隐式动画【英文标题】:Subclassing CALayer + Implicit Animation w/ Swift3 【发布时间】:2016-11-24 23:36:09 【问题描述】:

我正在尝试通过覆盖 CALayer 来实现“隐式动画”。我已经阅读了文档,发现它相当复杂。此外,我找不到任何像样的代码示例,也找不到很好地解释它或使用任何 Swift 的互联网帖子。

我已经通过子类BandBaseCG 实现,它试图为属性param1 提供隐式动画,并覆盖needsDisplay(forKey:),它在我的属性中调用一次,我返回true。

但这就是问题所在。我的理解是我必须返回一个符合CAAction 协议的对象,比如CABasicAnimation,这就是我所做的。但是,action(forKey:)defaultAction(forKey:) 不会被 param1 键调用。

所以我知道在某些时候我可以在draw(in ctx:) 中提供Core-Graphics 绘图代码来实现实际的隐式动画以响应一些layer.param1 = 2。但我无法做到这一点,因为 API 根本没有按我的预期工作。

这是我当前的实现:

import Foundation
import QuartzCore

class BandBaseCG: CALayer 

   @NSManaged var param1: Float

   override init(layer: Any) 

      super.init(layer: layer)

   

   override init() 
      super.init()

   

   required init?(coder aDecoder: NSCoder) 
      fatalError("init(coder:) has not been implemented")
   


   override func action(forKey event: String) -> CAAction? 

      print(event)

      if event == "param1" 

         let ba = CABasicAnimation(keyPath: event)
         ba.duration = 2
         ba.fromValue = (presentation()! as BandBaseCG).param1
         ba.toValue = 2

         return ba
      

      return super.action(forKey: event)
   


   override func draw(in ctx: CGContext) 
      print("draw")
   


   override class func defaultAction(forKey event: String) -> CAAction?     

      if event == "param1" 

         let ba = CABasicAnimation(keyPath: event)
         ba.duration = 2
         ba.fromValue = 0
         ba.toValue = 2

         return ba
      

      return super.defaultAction(forKey: event)
   

   override class func needsDisplay(forKey key: String) -> Bool 

      print(key)

      if key == "param1" 
         return true
      

      return super.needsDisplay(forKey: key)
   

   override func display() 
      print("param value: \(param1)")
   


【问题讨论】:

【参考方案1】:

这是一个完整的工作示例(来自我的书中):

class MyView : UIView  // exists purely to host MyLayer
    override class var layerClass : AnyClass 
        return MyLayer.self
    
    override func draw(_ rect: CGRect)  // so that layer will draw itself

class MyLayer : CALayer 
    @NSManaged var thickness : CGFloat
    override class func needsDisplay(forKey key: String) -> Bool 
        if key == #keyPath(thickness) 
            return true
        
        return super.needsDisplay(forKey:key)
    
    override func draw(in con: CGContext) 
        let r = self.bounds.insetBy(dx:20, dy:20)
        con.setFillColor(UIColor.red.cgColor)
        con.fill(r)
        con.setLineWidth(self.thickness)
        con.stroke(r)
    
    override func action(forKey key: String) -> CAAction? 
        if key == #keyPath(thickness) 
            let ba = CABasicAnimation(keyPath: key)
            ba.fromValue = self.presentation()!.value(forKey:key)
            return ba
        
        return super.action(forKey:key)
    

你会发现,如果你在你的app里放了一个MyView,你可以说

let lay = v.layer as! MyLayer
lay.thickness = 10 // or whatever

并且它将动画边框厚度的变化。

【讨论】:

“此外,我找不到任何像样的代码示例,也找不到很好地解释它或使用任何 Swift 的互联网帖子。”好的,我认为这是一个“体面”的代码示例。至于解释,你显然没有读过我的书,这不是你的错。但它确实存在,我认为它很好地解释了该代码的所有部分(当然只是我的看法)。 我认为这不公平。我花了几个小时在互联网上搜索,发现关于这个问题的信息很少。至于你的书,我很高兴知道它,将来一定会查阅它。感谢您的回复。 放松。我这不是你的错!无害无犯。我已经给了你代码,你会发现它可以工作(后面有一个可下载的完整项目)。我们在这里是同一个团队。 对不起,我没听懂“语气”。你的权利,这是一个很好的例子,我很感激。具体来说,似乎#keypath(我在阅读的任何教程中都没有提到,并且是一个有点晦涩的语言功能)是我需要的关键功能差异,是吗? @C0D3 你必须想想 Cocoa / Objective-C 在幕后做了什么。 Swift 只是一种与之交谈的方式。所以,Swift @NSManaged 是 Objective-C dynamic,这意味着 setter/getter 将在运行时合成,这就是我们在这里需要的,就像在 Core Data 中一样。请参阅apeth.com/iosBook/ch17.html#_making_a_property_animatable,了解在此处“翻译”为 Swift 的底层 Objective-C 机制。 (Swift dynamic 完全是另外一回事。)

以上是关于使用 Swift3 子类化 CALayer + 隐式动画的主要内容,如果未能解决你的问题,请参考以下文章

CALayer 的子类 VS 类别

NSTextField 子类化后淡出

选择性地覆盖 CALayer 隐式动画

通过 AVExportSession 导出带有隐式动画的 CALayer

在 -[CALayer setNeedsDisplayInRect:] 中禁用隐式动画

如何将 CALayer 子类化为另一个 CALayer 掩码?