Search code examples
iphoneobjective-cioscore-animationcore-graphics

Unpredictable behavior with Core Animation (CABasicAnimation)


I was writing a line a animation for iOS, where a line is drawn on a CALayer as a set of points in a CGPath. The line animates from a flat position to a 'molded' shape with different y co-ordinates but always the same x co-ordinates kind of like a line chart animation.

To make point interpolation easier I added a custom CGFloat property to my CALayer subclass. I call this property 'animator' (a better name might be progress, interpolator etc.) The idea was to add a simple CABasicAnimation on this property from 0.0f to 1.0f that way I get to use Core Animation's timing function and interpolation support while still being able to easily write custom animations. For example, if a line's point is to go from y = 100 to y = 130 then when the animator is at 0.0f I am at 100 when its 1.0f then I am at point 130, interim values give me my interim points and I keep re-drawing the line with these new points to get my animation.

Now the animation works fine, I have disabled layer actions, added needsDisplayForKey etc. but my problem is Core Animation is not accurate. The final value of animator is sometimes .95 .96 etc. and not 1.0. Well this is fine since floating point numbers have accuracy issues, however when my model value gets updated (set to 1.0f before adding the animation to the layer) the line should re-draw and I should get an accurate visual.

This is where another problem arises. Sometimes the animation is not removed immediately. Most of the times it gets removed instantly and I have no issues but sometimes it stays on for seconds, sometimes even minutes. To test my theory that the animation is not getting removed I added a simple BOOL flag to my layer which returns YES when we are on the presentation layer and sure enough sometimes I see my last drawInContext call was on the presentation layer with an animator value of 0.98967f or something and the final drawInContext call with animator 1.0f and the presentation layer flag being NO happens much later. As a result my visual jumps and is not accurate apart from the obvious horrible user experience.

I have tried to explain my problem the best I can, if anyone would like to see the code I would be glad to provide my test project. Here's hoping some smart person sees this and can help me out.

edit 1: Have uploaded the entire Xcode project (including my nasty edits, to show what all I've tried) here.

edit 2: someone with a similar problem who manually removes animations on completion http://lucas.tiz.ma/blog/2012/04/18/core-animation-is-a-bit-garbage-collection-y/

Thanks.


Solution

  • tl;dr Explicitly call setNeedsDisplay in the animationDidStop:finished: delegate call back.

    As per Lucas Tizma's post (see edit 2 above) I tried removing the animations manually. I opted for a simpler approach than his block based one by setting layers as the delegates of the animations set on them. This way the animation start and stop call backs come directly to the concerned layer. Well explicitly removing animations in the animationDidStop:finished: call back does not fix the issue. For example sometimes (see log below especially the time stamps) the animation will stop and checking that it's removed shows that it has been removed however an accurate draw with the actual model value happens much later.

    // animationDidStop:finished: call back code
    
    NSLog(@"layer animation stopped");
    
    // check if any animations exist
    NSLog(@"animation keys: %@", [self animationKeys]);
    
    // remove animations and check
    NSLog(@"removing animations");
    [self removeAllAnimations];
    NSLog(@"animation keys: %@", [self animationKeys]);
    
    // log
    
    2012-10-11 11:47:16.774 ASPathAnimationTest[3017:c07] on presentation layer 1
    2012-10-11 11:47:16.774 ASPathAnimationTest[3017:c07] 335 animation draw update, animator is 0.982606
    2012-10-11 11:47:16.775 ASPathAnimationTest[3017:c07] startPoint: {100, 90} - endPoint: {100, 100} - newPoint: {100, 99.8261}
    <snip>
    2012-10-11 11:47:16.791 ASPathAnimationTest[3017:c07] startPoint: {1000, 50} - endPoint: {1000, 100} - newPoint: {1000, 99.1303}
    2012-10-11 11:47:16.792 ASPathAnimationTest[3017:c07] layer animation stopped
    2012-10-11 11:47:16.792 ASPathAnimationTest[3017:c07] animation keys: (null)
    2012-10-11 11:47:16.793 ASPathAnimationTest[3017:c07] removing animations
    2012-10-11 11:47:16.793 ASPathAnimationTest[3017:c07] animation keys: (null)
    2012-10-11 11:47:16.794 ASPathAnimationTest[3017:c07] layer animation stopped
    2012-10-11 11:47:16.794 ASPathAnimationTest[3017:c07] animation keys: (null)
    2012-10-11 11:47:16.794 ASPathAnimationTest[3017:c07] removing animations
    2012-10-11 11:47:16.795 ASPathAnimationTest[3017:c07] animation keys: (null)
    2012-10-11 11:47:16.795 ASPathAnimationTest[3017:c07] layer animation stopped
    2012-10-11 11:47:16.819 ASPathAnimationTest[3017:c07] animation keys: (null)
    2012-10-11 11:47:16.820 ASPathAnimationTest[3017:c07] removing animations
    2012-10-11 11:47:16.820 ASPathAnimationTest[3017:c07] animation keys: (null)
    2012-10-11 11:47:16.821 ASPathAnimationTest[3017:c07] layer animation stopped
    2012-10-11 11:47:16.821 ASPathAnimationTest[3017:c07] animation keys: (null)
    2012-10-11 11:47:16.821 ASPathAnimationTest[3017:c07] removing animations
    2012-10-11 11:47:16.822 ASPathAnimationTest[3017:c07] animation keys: (null)
    2012-10-11 11:47:16.822 ASPathAnimationTest[3017:c07] layer animation stopped
    2012-10-11 11:47:16.822 ASPathAnimationTest[3017:c07] animation keys: (null)
    2012-10-11 11:47:16.823 ASPathAnimationTest[3017:c07] removing animations
    2012-10-11 11:47:16.823 ASPathAnimationTest[3017:c07] animation keys: (null)
    2012-10-11 11:48:00.000 ASPathAnimationTest[3017:c07] on presentation layer 0
    2012-10-11 11:48:00.000 ASPathAnimationTest[3017:c07] 336 animation draw update, animator is 1.000000
    <snip, there are 5 lines so draw and point logs go here>
    2012-10-11 11:48:00.021 ASPathAnimationTest[3017:c07] 340 animation draw update, animator is 1.000000
    2012-10-11 11:48:00.023 ASPathAnimationTest[3017:c07] startPoint: {100, 90} - endPoint: {100, 100} - newPoint: {100, 100}
    <snip>
    2012-10-11 11:48:00.026 ASPathAnimationTest[3017:c07] startPoint: {1000, 50} - endPoint: {1000, 100} - newPoint: {1000, 100}
    

    After seeing the log and noticing the fact that animations really are being removed it's just that the re-draw with the actual accurate model value sometimes does not happen until seconds or even minutes later I changed the animationDidStop:finished: delegate call back to explicitly call setNeedsDisplay on the layer. Bingo.