CABasicAnimation creates empty default value copy

2019-06-07 14:56发布

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:

debug class

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.

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

improper

Here's what is should look like:

correct

Here's the animation code:

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)

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)

  }
}

1条回答
可以哭但决不认输i
2楼-- · 2019-06-07 15:35

Figured out the answer!

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)
}
查看更多
登录 后发表回答