0

I would like to (ab-)use Core Animation or even UIDynamics to generate animated values (floats) that I could use to control other parameters.

E.g. Having an animation with an ease in, I would like to control a color with an 'eased' value.

CIFilter
  • 8,647
  • 4
  • 46
  • 66
brainray
  • 12,512
  • 11
  • 67
  • 116

2 Answers2

3

Core Animation can certainly do this for you. You need to create a CALayer subclass that has a new property you'd like to animate:

class CustomLayer: CALayer {

    @NSManaged var colorPercentage: Float

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

        if let layer = layer as? CustomLayer {
            colorPercentage = layer.colorPercentage
        } else {
            colorPercentage = 0.0
        }
    }

    required init?(coder decoder: NSCoder) {
        super.init(coder: decoder)
    }

    override class func needsDisplay(forKey key: String) -> Bool {
        var needsDisplay = super.needsDisplay(forKey: key)

        if key == "colorPercentage" {
            needsDisplay = true
        }

        return needsDisplay
    }

    override func action(forKey key: String) -> CAAction? {
        var action = super.action(forKey: key)

        if key == "colorPercentage" {
            action = super.action(forKey: "opacity")     // Create reference action from an existing CALayer key

            if let action = action as? CABasicAnimation {
                action.keyPath = key
                action.fromValue = value(forKeyPath: key)
            }
        }

        return action
    }

    override func display() {
        guard let presentationLayer = presentation() else { return }

        print("Current 'colorPercentage' value: \(presentationLayer.colorPercentage)")
        // Called for every frame of an animation involving "colorPercentage"
    }
}

(In Swift, @NSManaged is a hack currently necessary for Core Animation to treat colorPercentage as an Objective-C @dynamic property.)

Following this pattern, you can create a custom layer that contains your own animatable properties. display() will be called for every frame of the animation on the main thread, so you can respond to value changes over time however you'd like:

let animation = CABasicAnimation(keyPath: "colorPercentage")
animation.duration = 1.0
animation.fromValue = customLayer.colorPercentage

customLayer.colorPercentage = 1.0
customLayer.add(animation, forKey: "colorPercentageAnimation")

As a bonus, action(forKey:) can be used to create an action from a "reference animation" that Core Animation knows about and configures it to work with your custom property. With this, it's possible to invoke CALayer animations inside UIKit-style animation closures, which are much more convenient to use:

UIView.animate(withDuration: 1.0, animations: {
    customLayer.colorPercentage = 1.0            
})
CIFilter
  • 8,647
  • 4
  • 46
  • 66
0

My best guess is to use CADisplayLink. See this post. Changes of an animation are not observable by KVO.

brainray
  • 12,512
  • 11
  • 67
  • 116
  • 1
    This would be most useful for **monitoring** the presentation value of an existing animatable property, like the opacity of some view, to respond to visual changes to that value. But for custom properties, it's cleaner to make a `CALayer` subclass that defines such custom properties as animatable. – CIFilter Aug 16 '16 at 01:17