Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why when I set a low duration value for my CABasicAnimation does it jump?

Sample Project: http://cl.ly/1W3V3b0D2001

I'm using CABasicAnimation to create a progress indicator that is like a pie chart. Similar to the iOS 7 app download animation:

enter image description here

The animation is set up as follows:

- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];

    CGFloat radius = CGRectGetWidth(self.frame) / 2;
    CGFloat inset  = 1;
    CAShapeLayer *ring = [CAShapeLayer layer];
    ring.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
                                           cornerRadius:radius-inset].CGPath;

    ring.fillColor = [UIColor clearColor].CGColor;
    ring.strokeColor = [UIColor whiteColor].CGColor;
    ring.lineWidth = 2;

    self.innerPie = [CAShapeLayer layer];
    inset = radius/2;
    self.innerPie.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
                                               cornerRadius:radius-inset].CGPath;
    self.innerPie.fillColor = [UIColor clearColor].CGColor;
    self.innerPie.strokeColor = [UIColor whiteColor].CGColor;
    self.innerPie.lineWidth = (radius-inset)*2;

    self.innerPie.strokeStart = 0;
    self.innerPie.strokeEnd = 0;

    [self.layer addSublayer:ring];
    [self.layer addSublayer:self.innerPie];

    self.progress = 0.0;
}

The animation is triggered by setting the progress of the view:

- (void)setProgress:(CGFloat)progress animated:(BOOL)animated {
    self.progress = progress;

    if (animated) {
        CGFloat totalDurationForFullCircleAnimation = 0.25;

        CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        self.innerPie.strokeEnd = progress;
        pathAnimation.delegate = self;
        pathAnimation.fromValue = @([self.innerPie.presentationLayer strokeEnd]);
        pathAnimation.toValue = @(progress);
        pathAnimation.duration = totalDurationForFullCircleAnimation * ([pathAnimation.toValue floatValue] - [pathAnimation.fromValue floatValue]);

        [self.innerPie addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
    }
    else {
        [CATransaction setDisableActions:YES];
        [CATransaction begin];
        self.innerPie.strokeEnd = progress;
        [CATransaction commit];
    }
}

However, in cases where I set the progress to something small, such as 0.25, there's a jump in the animation. It goes a little forward clockwise, jumps back, then keeps going forward as normal. It's worth nothing that this does not happen if the duration or progress is set higher.

How do I stop the jump? This code works well in every case except when the progress is very low. What am I doing wrong?

like image 739
Doug Smith Avatar asked Mar 20 '14 01:03

Doug Smith


1 Answers

Ah! We should have seen this earlier. The problem is this line in your if (animated) block:

self.innerPie.strokeEnd = progress;

Since innerPie is a Core Animation layer, this causes an implicit animation (most property changes do). This animation is fighting with your own animation. You can prevent this from happening by disabling implicit animation while setting the strokeEnd:

[CATransaction begin];
[CATransaction setDisableActions:YES];
self.innerPie.strokeEnd = progress;
[CATransaction commit];

(Note how setDisableActions: is within the begin/commit.)

Alternatively, you can remove your own animation and just use the automatic one, using something like setAnimationDuration: to change its length.

Original Suggestions:

My guess is that your drawRect: is being called, which resets your strokeEnd value. Those layers should probably not be set up in drawRect: anyway. Try moving that setup to an init method or didMoveToWindow: or similar.

If that's not effective, I would suggest adding log statements to track the value of progress and [self.innerPie.presentationLayer strokeEnd] each time the method is called; perhaps they're not doing what you expect.

like image 196
Jesse Rusak Avatar answered Oct 20 '22 01:10

Jesse Rusak