CABasicAnimation创建CALayer的空默认值副本

问题描述:

我有一个自定义的CALayer,它绘制径向渐变.除动画期间外,它的效果都很好.似乎CABasicAnimation的每次迭代都会创建CALayer子类的新副本,并带有空的默认属性值:

I have a custom CALayer that draws radial gradients. It works great except during animation. It seems that each iteration of CABasicAnimation creates a new copy of the CALayer subclass with empty, default values for the properties:

在上面的屏幕截图中,您看到CABasicAnimation已经创建了该图层的新副本并正在更新gradientOrigin,但是没有其他属性可供使用.

In the screenshot above, you see that CABasicAnimation has created a new copy of the layer and is updating gradientOrigin but none of the other properties have come along for the ride.

其结果是在动画期间不渲染任何内容.这是GIF:

This has the result of not rendering anything during the animation. Here's a GIF:

这是应该的样子:

这是动画代码:

let animation = CABasicAnimation(keyPath: "gradientOrigin")
animation.duration = 2
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
let newOrigin: CGPoint = CGPoint(x: 0, y: triangle.bounds.height/2)
animation.fromValue = NSValue(CGPoint: triangle.gradientLayer.gradientOrigin)
animation.toValue = NSValue(CGPoint: newOrigin)
triangle.gradientLayer.gradientOrigin = newOrigin
triangle.gradientLayer.addAnimation(animation, forKey: nil)

这是自定义CALayer代码:

Here's the custom CALayer code:

enum RadialGradientLayerProperties: String {
  case gradientOrigin
  case gradientRadius
  case colors
  case locations
}

class RadialGradientLayer: CALayer {
  var gradientOrigin = CGPoint() {
    didSet { setNeedsDisplay() }
  }
  var gradientRadius = CGFloat() {
    didSet { setNeedsDisplay() }
  }

  var colors = [CGColor]() {
    didSet { setNeedsDisplay() }
  }

  var locations = [CGFloat]() {
    didSet { setNeedsDisplay() }
  }

  override init(){
    super.init()
    needsDisplayOnBoundsChange = true
  }

  required init(coder aDecoder: NSCoder) {
    super.init()
  }

  override init(layer: AnyObject) {
    super.init(layer: layer)
  }

  override class func needsDisplayForKey(key: String) -> Bool {
    if key == RadialGradientLayerProperties.gradientOrigin.rawValue || key == RadialGradientLayerProperties.gradientRadius.rawValue || key == RadialGradientLayerProperties.colors.rawValue || key == RadialGradientLayerProperties.locations.rawValue {
      print("called \(key)")
      return true
    }
    return super.needsDisplayForKey(key)

  }

  override func actionForKey(event: String) -> CAAction? {
    if event == RadialGradientLayerProperties.gradientOrigin.rawValue || event == RadialGradientLayerProperties.gradientRadius.rawValue || event == RadialGradientLayerProperties.colors.rawValue || event == RadialGradientLayerProperties.locations.rawValue {
      let animation = CABasicAnimation(keyPath: event)
      animation.fromValue = self.presentationLayer()?.valueForKey(event)
      return animation
    }
    return super.actionForKey(event)
  }

  override func drawInContext(ctx: CGContext) {

    guard let colorRef = self.colors.first else { return }

    let numberOfComponents = CGColorGetNumberOfComponents(colorRef)
    let colorSpace = CGColorGetColorSpace(colorRef)

    let deepGradientComponents: [[CGFloat]] = (self.colors.map {
      let colorComponents = CGColorGetComponents($0)
      let buffer = UnsafeBufferPointer(start: colorComponents, count: numberOfComponents)
      return Array(buffer) as [CGFloat]
      })

    let flattenedGradientComponents = deepGradientComponents.flatMap({ $0 })

    let gradient = CGGradientCreateWithColorComponents(colorSpace, flattenedGradientComponents, self.locations, self.locations.count)

    CGContextDrawRadialGradient(ctx, gradient, self.gradientOrigin, 0, self.gradientOrigin, self.gradientRadius, .DrawsAfterEndLocation)

  }
}

找出答案!

init(layer:)中,您必须将属性值手动复制到您的类中.这是实际的效果:

In init(layer:) you have to copy the property values to your class manually. Here's how that looks in action:

override init(layer: AnyObject) {
  if let layer = layer as? RadialGradientLayer {
    gradientOrigin = layer.gradientOrigin
    gradientRadius = layer.gradientRadius
    colors = layer.colors
    locations = layer.locations
  }
  super.init(layer: layer)
}