带Swift3的CALayer +隐式动画子类
我试图通过重写CALayer来实现隐式动画。我通读了文档,发现它相当复杂。而且,我找不到任何不错的代码示例,也找不到很好的解释或使用任何Swift的互联网帖子。
I am trying to implement "implicit animations" by overriding CALayer. I've read through the documentation and find it rather complicated. More over, I can't find any decent code examples, or internet posts explaining it well or using any Swift.
我由子类 BandBaseCG
实现,该子类试图为属性 param1提供隐式动画
,并覆盖了 needsDisplay(forKey:)
,它的确会用我的属性调用一次,并返回true。
I've implemented by subclass BandBaseCG
which attempts to provide implicit animation for property param1
, and overridden needsDisplay(forKey:)
which does get called once with my property where I return true.
但是这里出了问题。我的理解是,我必须返回一个符合 CAAction
协议的对象,比如说 CABasicAnimation
,这就是我的目的做。但是, action(forKey:)
或 defaultAction(forKey:)
不会与 param1
键。
But here is where it goes wrong. My understanding is that I must return an object conforming to the CAAction
protocol, say a CABasicAnimation
, and this is what I do. However, action(forKey:)
or defaultAction(forKey:)
do not get called with the param1
key.
因此,我了解到可以在 draw(ctx:)
中提供Core-Graphics绘图代码响应某些 layer.param1 = 2
来实现实际的隐式动画。但是我到此为止,因为API根本无法按我预期的那样工作。
So I understand that at some point I can provide Core-Graphics drawing code in draw(in ctx:)
to implement the actual implicit animation in response to some layer.param1 = 2
. But I just cannot get to this point because the API is simply not working how I expected.
这是我当前的实现方式:
Here is my current implementation:
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)")
}
}
工作示例(来自我的书):
Here's a complete working example (from my book):
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)
}
}
您会发现,如果将MyView放入应用程序中,则可以说
You will find that if you put a MyView into your app, you can say
let lay = v.layer as! MyLayer
lay.thickness = 10 // or whatever
它将使更改生效在边框的厚度。
and that it will animate the change in the border thickness.