1

I have a custom CALayer with an animatable property called progress. The basic code is:

@implementation AnimatedPieChartLayer

@dynamic progress;

+ (BOOL)needsDisplayForKey:(NSString *)key {
    return [key isEqualToString: @"progress"] || [super needsDisplayForKey: key];
}

- (id <CAAction>)actionForKey:(NSString *)key {
    if ([self presentationLayer] != nil) {
        if ([key isEqualToString: @"progress"]) {
            CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath: key];
            [anim setFromValue: [[self presentationLayer] valueForKey: key]];
            [anim setDuration: 0.75f];
            return anim;
        }
    }
    return [super actionForKey: key];
}

- (void)drawInContext:(CGContextRef)context {
  // do stuff
}

@end

In the view controller I do this, to try and get my animation to start from the beginning every time:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear: animated];

    [pieChart setProgress: 0.0];
}

This all works perfectly. I put the layer on a view, the view in a view controller, and it all works as expected.

The complication is that my view controller is inside a UIScrollView. This presents a situation where the user could swipe away from my view controller before the CALayer animation completes. If they swipe back quickly, the CALayer animation does something weird. It reverses. It's like the animation is trying to go back to the beginning. Once it gets there it starts over, and animates the way it is supposed to.

So the question is, how can I prevent this. I want the animation to start from the beginning every time the view is displayed – regardless of what happened last time.

Any suggestions?

UPDATE

Here's a visual example of the problem:

Working:

enter image description here

Failing:

enter image description here

Axeva
  • 4,697
  • 6
  • 40
  • 64

2 Answers2

3

In the setup you have there, you have an implicit animation for the key "progress" so that whenever the progress property of the layer changes, it animates. This works both when the value increases and when it decreases (as seen in your second image).

To restore the layer to the default 0 progress state without an animation, you can wrap the property change in a CATransaction where all actions are disabled. This will disable the implicit animation so that you can start over from 0 progress.

[CATransaction begin];
[CATransaction setDisableActions:YES]; // all animations are disabled ...
[pieChart setProgress: 0.0];
[CATransaction commit]; // ... until this line
David Rönnqvist
  • 56,267
  • 18
  • 167
  • 205
  • I agree. This is my estimation for the cause of the problem as well, but the solution is not working. I've added this code in my `viewWillAppear` and my `viewWillDisappear`. It still doesn't prevent the decrease animation. Something is not adding up. – Axeva Feb 17 '14 at 19:24
  • @Axeva You are right, that doesn't add up. I did a quick experiment myself and that didn't animate. Can you add the code for all the places where the progress changes? – David Rönnqvist Feb 17 '14 at 19:43
  • I may have found the issue. To make the animation delay before it starts, I'm using GCD. Something like this: `dispatch_time_t animationStart = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.75 * NSEC_PER_SEC)); dispatch_after(animationStart, dispatch_get_main_queue(), ^(void){ [pieChart setProgress: 0.1625]; });` I think GCD is messing up the animation cancel. Something about being in a different thread perhaps. – Axeva Feb 17 '14 at 20:37
  • 2
    You aren't canceling something that hasn't happened yet. If you want to delay an animation, consider using an explicit animation with a `beginTime` – David Rönnqvist Feb 17 '14 at 20:42
  • David – I'll give you credit for the answer. It was actually the `beginTime` follow-up that lead to the actual solution I needed. Thank you! – Axeva Feb 19 '14 at 14:53
  • 1
    @Axeva I can take the upvote for being "useful" (as the tooltip says) but my answer wasn't the solution. You can post the final solution yourself and accept it. In case someone else has the same or a very similar problem it might help them more than reading through all the comments on this answer. – David Rönnqvist Feb 21 '14 at 12:37
0

Probably too simple a suggestion to merit an 'answer,' but I would suggest canceling the animation. Looks like you could rewrite this code pretty easily so that the progress updates were checking some complete flag and then when the view get hidden, kill it. This thread has some good ideas on it.

Community
  • 1
  • 1
Rob
  • 11,446
  • 7
  • 39
  • 57
  • Thanks for the response. I've tried that, but it didn't have the effect I wanted. Maybe I wasn't doing it properly. Where would I out the animation cancel? On the ViewWillDisappear? – Axeva Feb 16 '14 at 14:57
  • Yes. Clearly if you are not going to be visible, there is no reason to take any more cycles. – Rob Feb 16 '14 at 15:21
  • I tried adding `[[pieChart layer] removeAllAnimations];` in my `viewWillDisappear` method, but still no luck. The result is the second animation I posted in my original question. – Axeva Feb 17 '14 at 18:51