Core animation progress callback

uiviewpropertyanimator
caanimationgroup
catransaction
cabasicanimation
calayer
camediatiming
cakeyframeanimation
core animation properties

Is there an easy way to be called back when a Core Animation reaches certain points as it's running (for example, at 50% and 66% of completion ?

I'm currently thinking about setting up an NSTimer, but that's not really as accurate as I'd like.

How to monitor Core animation progress callback?, How to monitor Core animation progress callback?, Programmer Sought, the best programmer technical posts sharing site. This class provides a simple callback mechanism to listeners that is synchronized with all other animators in the system. TypeConverter: Abstract base class used convert type T to another type V. ValueAnimator: This class provides a simple timing engine for running animations which calculate animated values and set them on target objects.

If you don't want to hack a CALayer to report progress to you, there's another approach. Conceptually, you can use a CADisplayLink to guarantee a callback on each frame, and then simply measure the time that has passed since the start of the animation divided by the duration to figure out the percent complete.

The open source library INTUAnimationEngine packages this functionality up very cleanly into an API that looks almost exactly like the UIView block-based animation one:

// INTUAnimationEngine.h

// ...

+ (NSInteger)animateWithDuration:(NSTimeInterval)duration
                           delay:(NSTimeInterval)delay
                      animations:(void (^)(CGFloat percentage))animations
                      completion:(void (^)(BOOL finished))completion;

// ...

All you need to do is call this method at the same time you start other animation(s), passing the same values for duration and delay, and then for each frame of the animation the animations block will be executed with the current percent complete. And if you want peace of mind that your timings are perfectly synchronized, you can drive your animations exclusively from INTUAnimationEngine.

Advanced Animation Tricks, Core Animation Programming Guide. PDF Companion File. Table of Contents. Jump To… Download Sample Code. Introduction · Core  core:progress_on_uicomponent_animation_finished(uicomponent uicomponent, function callback) Calls the supplied callback once the supplied component has finished animating. This function polls the animation state every 1/10th of a second, so there may be a slight unavoidable delay between the animation finishing and the supplied callback being called.

I made a Swift (2.0) implementation of the CALayer subclass suggested by tarmes in the accepted answer:

protocol TAProgressLayerProtocol {

    func progressUpdated(progress: CGFloat)

}

class TAProgressLayer : CALayer {

    // MARK: - Progress-related properties

    var progress: CGFloat = 0.0
    var progressDelegate: TAProgressLayerProtocol? = nil

    // MARK: - Initialization & Encoding

    // We must copy across our custom properties since Core Animation makes a copy
    // of the layer that it's animating.

    override init(layer: AnyObject) {
        super.init(layer: layer)
        if let other = layer as? TAProgressLayerProtocol {
            self.progress = other.progress
            self.progressDelegate = other.progressDelegate
        }
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        progressDelegate = aDecoder.decodeObjectForKey("progressDelegate") as? CALayerProgressProtocol
        progress = CGFloat(aDecoder.decodeFloatForKey("progress"))
    }

    override func encodeWithCoder(aCoder: NSCoder) {
        super.encodeWithCoder(aCoder)
        aCoder.encodeFloat(Float(progress), forKey: "progress")
        aCoder.encodeObject(progressDelegate as! AnyObject?, forKey: "progressDelegate")
    }

    init(progressDelegate: TAProgressLayerProtocol?) {
        super.init()
        self.progressDelegate = progressDelegate
    }

    // MARK: - Progress Reporting

    // Override needsDisplayForKey so that we can define progress as being animatable.
    class override func needsDisplayForKey(key: String) -> Bool {
        if (key == "progress") {
            return true
        } else {
            return super.needsDisplayForKey(key)
        }
    }

    // Call our callback

    override func drawInContext(ctx: CGContext) {
        if let del = self.progressDelegate {
            del.progressUpdated(progress)
        }
    }

}

Interactive Animations · objc.io, Differentiating Keypath From Key. Each core animation layer contains a dictionary that enables you to add and remove animations based on a  The onProgress and onComplete callbacks are useful for synchronizing an external draw to the chart animation. The callback is passed following object: { // Chart object chart: Chart, // Number of animations still in progress currentStep: number, // Total number of animations at the start of current animation numSteps: number, }

Ported to Swift 4.2:
protocol CAProgressLayerDelegate: CALayerDelegate {
    func progressDidChange(to progress: CGFloat)
}

extension CAProgressLayerDelegate {
    func progressDidChange(to progress: CGFloat) {}
}

class CAProgressLayer: CALayer {
    private struct Const {
        static let animationKey: String = "progress"
    }

    @NSManaged private(set) var progress: CGFloat
    private var previousProgress: CGFloat?
    private var progressDelegate: CAProgressLayerDelegate? { return self.delegate as? CAProgressLayerDelegate }

    override init() {
        super.init()
    }

    init(frame: CGRect) {
        super.init()
        self.frame = frame
    }

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

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.progress = CGFloat(aDecoder.decodeFloat(forKey: Const.animationKey))
    }

    override func encode(with aCoder: NSCoder) {
        super.encode(with: aCoder)
        aCoder.encode(Float(self.progress), forKey: Const.animationKey)
    }

    override class func needsDisplay(forKey key: String) -> Bool {
        if key == Const.animationKey { return true }
        return super.needsDisplay(forKey: key)
    }

    override func display() {
        super.display()
        guard let layer: CAProgressLayer = self.presentation() else { return }
        self.progress = layer.progress
        if self.progress != self.previousProgress {
            self.progressDelegate?.progressDidChange(to: self.progress)
        }
        self.previousProgress = self.progress
    }
}
Usage:
class ProgressView: UIView {
    override class var layerClass: AnyClass {
        return CAProgressLayer.self
    }
}

class ExampleViewController: UIViewController, CAProgressLayerDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()

        let progressView = ProgressView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        progressView.layer.delegate = self
        view.addSubview(progressView)

        var animations = [CAAnimation]()

        let opacityAnimation = CABasicAnimation(keyPath: "opacity")
        opacityAnimation.fromValue = 0
        opacityAnimation.toValue = 1
        opacityAnimation.duration = 1
        animations.append(opacityAnimation)

        let progressAnimation = CABasicAnimation(keyPath: "progress")
        progressAnimation.fromValue = 0
        progressAnimation.toValue = 1
        progressAnimation.duration = 1
        animations.append(progressAnimation)

        let group = CAAnimationGroup()
        group.duration = 1
        group.beginTime = CACurrentMediaTime()
        group.animations = animations

        progressView.layer.add(group, forKey: nil)
    }

    func progressDidChange(to progress: CGFloat) {
        print(progress)
    }
}

Core Animation Tutorial: Interrupting Animation Progress, Core Animation, the Cocoa framework responsible for most iOS and macOS animations, is a complex system with a lot of hidden and  This class provides a simple callback mechanism to listeners that is synchronized with all other animators in the system. There is no duration, interpolation, or object value-setting with this Animator.

Better iOS Animations with CATransaction, See also progress indicators application badges, updating, 416–417 audio alerts​, 236–237 callbacks, 237 Core Animation calls, 244–246 Core Animation  Defines what this animation should do when it reaches the end. long: getStartDelay() The amount of time, in milliseconds, to delay starting the animation after start() is called. long: getTotalDuration() Gets the total duration of the animation, accounting for animation sequences, start delay, and repeating. PropertyValuesHolder[]

The iPhone Developer's Cookbook: Building Applications with the , Learn how to add an eye-catching circular loading animation to your iOS show you how to recreate this exact effect in Swift and Core Animation. do with your indicator is to update progress in the image download callback. There are no progress callback features of System.Net.Http.HttpClient. There are no plans to add this. The feature set of CoreFx HttpClient matches that of.NET Framework (Desktop). You can add this feature yourself by using the appropriate APIs to "download" content.

How To Implement A Circular Image Loader Animation with , A simple and customizable animated progress bar written in Swift. but I hope it will push you into the right direction in getting started with Core Animation! Synchronized animation with other properties, delays, completion callbacks and​  From interface androidx.core.animation.TypeEvaluator abstract Integer evaluate (float fraction, Integer startValue, Integer endValue)

Comments
  • I don't know about easy... but how's about KVO on the property that you are manipulating.... it rings a bell that I might have done this before.
  • At certain points during the animation I want to display and hide other views.
  • Nice work. Note that you have to remove the animation from the layer explicitly when its done, otherwise drawInContext: will not stop getting called on every frame.
  • Awesome idea. I'd like to suggest a small improvement. You're currently forced to set the layer's frame to a 1x1 rect since drawInContext: isn't called unless the layer's frame is non-zero (creating a 1x1pt bitmap in the process). Instead you can override [CALayer display], which will be called for a zero frame, and won't create a bitmap context by default (marginally more efficient). The only difference is that you'd need to read self.presentationLayer.progress instead of self.progress within the draw implementation.
  • This should be the correct answer. The current accepted answer seems like a hack in comparison. This tutorial explains how to setup a basic CADisplayLink scheme: zearfoss.wordpress.com/2011/09/02/more-cadisplaylink You can also apply easing quite easily your self to this scheme. Try this tutorial to understand the basics of applying easing to FPS animation: medium.com/thoughts-on-thoughts/…
  • this tutorial also favours the CADisplayLink approach over the current accepted answer. holko.pl/2014/06/26/recreating-skypes-action-sheet-animation
  • Welcome to Stack Overflow. While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. How to Answer