MichalMoskala
MichalMoskala

Reputation: 927

Using CAShapeLayer to create UIActivityIndicator animation

I want to create animation that looks like UIActivityIndicator animation using CAShapeLayer stroke.

I've create layer:

self.ovalShapeLayer = [CAShapeLayer layer];

self.ovalShapeLayer.strokeColor = [UIColor redColor].CGColor;
self.ovalShapeLayer.fillColor = [UIColor clearColor].CGColor;
self.ovalShapeLayer.lineWidth = 15.0;

self.ovalShapeLayer.lineDashPattern = @[@1, @9];
self.ovalShapeLayer.lineDashPhase = 20.0;
self.ovalShapeLayer.lineJoin = kCALineJoinBevel;
self.ovalShapeLayer.strokeStart = 0.0;
self.ovalShapeLayer.strokeEnd = 1.0;

self.ovalShapeLayer.path = [UIBezierPath bezierPathWithOvalInRect:self.controlView.bounds].CGPath;

[self.controlView.layer addSublayer:self.ovalShapeLayer];

and it works very nice. I can also animate stroke end using

[CABasicAnimation animationWithKeyPath:@"strokeEnd"]

But the UIActivityIndicator animation is more complex because it's dashes are fading in/out which looks like rotation. Do you have any ideas how to achieve that animation? I was thinking about creating each dash with a new CAShapeLayer and then use a keyframe opacity animation but it seems wrong way.

Upvotes: 0

Views: 494

Answers (3)

John Wong
John Wong

Reputation: 352

I have spent much time to know how UIRefreshControl works, including UIActivityIndicator. Finally, I write a refresh control to avoid some issues in system refresh control. Here is the code of my activity indicator in refresh control: https://github.com/JohnWong/JWKit/blob/master/refresh-control/JWRefreshIndicatorView.m

If you use lldb to inspect system activity indicator and mine, you will find it is much the same as UIActivityIndicator. CAReplicatorLayer is the key to create the view.

Upvotes: 1

MichalMoskala
MichalMoskala

Reputation: 927

After some research I've found wonderful class - CAReplicatorLayer.

Using that class I was able to create one CALayer object and replicate it using CATransform3D.

Here is my code:

    self.replicatorLayer = [CAReplicatorLayer layer];
    self.replicatorLayer.frame = self.controlView.bounds;
    [self.controlView.layer addSublayer:self.replicatorLayer];

    self.dashLayer = [CALayer layer];
    CGFloat dotSide = 4.0;

    self.dashLayer.frame = CGRectMake(CGRectGetMidX(self.replicatorLayer.frame) - dotSide / 2, 0.0, dotSide, dotSide * 4);
    self.dashLayer.backgroundColor = [UIColor redColor].CGColor;
    self.dashLayer.cornerRadius = 1.5;
    self.dashLayer.cornerRadius = 2.5;
    [self.replicatorLayer addSublayer:self.dashLayer];

    self.replicatorLayer.instanceCount = 12;
    self.replicatorLayer.instanceTransform = CATransform3DMakeRotation(M_PI / 6, 0.0, 0.0, 1.0);
    self.replicatorLayer.instanceDelay = 1.0 / 12;

and animation looks like this:

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    animation.fromValue = @(1.0);
    animation.toValue = @(0.0);
    animation.duration = 1.0;
    animation.repeatCount = CGFLOAT_MAX;
    [self.dashLayer addAnimation:animation forKey:nil];

works like a charm ;)

Upvotes: 0

Ankit Thakur
Ankit Thakur

Reputation: 4749

You can take 1 loader image as in attachment, and rotate that image on z-axis.

CABasicAnimation* rotationAnimation;
    rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 /* full rotation*/ * rotations * duration ];
    rotationAnimation.duration = duration;
    rotationAnimation.cumulative = YES;
    rotationAnimation.repeatCount = repeat;

[imageView.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];

enter image description here

@interface CustomView (){
    __weak IBOutlet UIImageView *loaderImageView;
}

@end

@implementation CustomView

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        // Initialization code
//        loaderImageView = [[UIImageView alloc] initWithFrame:(CGRect){CGPointZero, 35, 35}];
//        loaderImageView.contentMode = UIViewContentModeScaleAspectFit;
//        [self addSubview:loaderImageView];

    }
    return self;
}

- (void) setUp{
    CABasicAnimation* rotationAnimation1;
    rotationAnimation1 = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    rotationAnimation1.toValue = @M_PI_2;
    rotationAnimation1.duration = 1.0;
    rotationAnimation1.cumulative = YES;
    rotationAnimation1.repeatCount = HUGE_VALF;
    [loaderImageView.layer addAnimation:rotationAnimation1 forKey:@"rotation"];
}

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    // Drawing code
}
*/

@end

Upvotes: 1

Related Questions