Search code examples
iosobjective-canimationcashapelayercgpath

Changing CAShapeLayer's path in for loop makes no effect


I'm changing CAShapeLayer's path in for loop, but it only shows the last result of loop. I know, that I can do that with CABasicAnimation but I need to change path this way. Any ideas how to do that?

UIBezierPath *path = [ShapeManager getCirclePathAppendedWithCirclePathWithRadius:radius
                                                                        andFrame:self.view.bounds];
self.layer.fillRule = kCAFillRuleEvenOdd;
self.layer.fillColor = [UIColor grayColor].CGColor;
self.layer.opacity = 0.5;
self.layer.path = path.CGPath;
[self.view.layer addSublayer:self.layer];
for (int i = 1; i < 100; ++i) {
    path = [ShapeManager getCirclePathAppendedWithCirclePathWithRadius:self.view.bounds.size.width-i
                                                              andFrame:self.view.bounds];
    self.layer.path = path.CGPath;
}

helper methods

+ (UIBezierPath *)getCirclePathAppendedWithCirclePathWithRadius:(CGFloat)radius andFrame:(CGRect)frame {
    UIBezierPath *backgroundPath = [UIBezierPath bezierPathWithRect:frame];
    [backgroundPath appendPath:[self getCirclePathWithRadius:radius andFrame:frame]];
    return backgroundPath;
}

+ (UIBezierPath *)getCirclePathWithRadius:(CGFloat)radius andFrame:(CGRect)frame {
    CGRect rect = frame;
    UIBezierPath *circlePath;
    circlePath = [UIBezierPath bezierPathWithArcCenter:(CGPoint){rect.size.width/2, rect.size.height/2} radius:radius startAngle:0 endAngle:M_PI*2 clockwise:YES];
    return circlePath;
}

Solution

  • You're right about using CABasicAnimation.

    1. Remove your for loop, it is not going to do the job.
    2. Add CABasicAnimation for path key with values to interpolate between. This code should be called only once in viewDidLoad method:

    UIBezierPath *fromPath = [self.class getCirclePathAppendedWithCirclePathWithRadius:self.view.bounds.size.width andFrame:self.view.bounds];
    UIBezierPath *toPath = [self.class getCirclePathAppendedWithCirclePathWithRadius:self.view.bounds.size.width-99 andFrame:self.view.bounds];
    
    CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"path"];
    animation.fromValue = (id)fromPath.CGPath;
    animation.toValue = (id)toPath.CGPath;
    animation.duration = 1; 
    
    [layer addAnimation:animation forKey:@"pathAnimation"];
    layer.speed = 0;
    

    Update:

    1. In the last line you set speed value to 0 because you're going to set animation progress manually:

    @property (nonatomic, assign) double progress;
    
    - (void) setProgress: (double) progress {
        _progress = progress;        
        layer.timeOffset = (CFTimeInterval)progress; 
    }
    

    This assumes that animation duration is 1.0. Otherwise you need to set

    layer.timeOffset = (CFTimeInterval)(progress * totalDuration);
    

    Now just set progress in motion callback according to the updated value.