Cutting an image to a CALayers content during an animation - ios

I have created a subclass of CALayer to draw a slice of a pie chart with a custom color (color), a startAngle (actualStartAngle), an endAngle (actualEndAngle) and an image (image)[This image should always be in the middle of the slice] as animating this is not possible with a normal CAShapeLayer. The layer is currently drawn like this:
- (void)drawInContext:(CGContextRef)ctx{
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, self.center.x, self.center.y);
CGContextAddArc(ctx, self.center.x, self.center.y, self.radius, self.actualEndAngle, self.actualStartAngle, YES);
CGContextClosePath(ctx);
CGContextSetFillColorWithColor(ctx, self.color);
CGContextSetLineWidth(ctx, 0);
CGContextDrawPath(ctx, kCGPathFillStroke);
if (self.image) {
CGContextDrawImage(ctx, [self getFrameForImgLayerWithStartAngle:self.actualStartAngle andEndAngle:self.actualEndAngle], self.image);
}
}
These individual slices are animated by:
[CATransaction begin];
CABasicAnimation *colorAnimation = [CABasicAnimation animationWithKeyPath:#"color"];
colorAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
colorAnimation.duration = duration;
colorAnimation.fromValue = (id)self.color;
colorAnimation.toValue = (id)colorTU.CGColor;
CABasicAnimation *startAngleAnimation = [CABasicAnimation animationWithKeyPath:#"actualStartAngle"];
startAngleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
startAngleAnimation.duration = duration;
startAngleAnimation.fromValue = [self valueForKey:#"actualStartAngle"];
startAngleAnimation.toValue = [[NSNumber alloc] initWithFloat:newStartAngle];
CABasicAnimation *endAngleAnimation = [CABasicAnimation animationWithKeyPath:#"actualEndAngle"];
endAngleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
endAngleAnimation.duration = duration;
endAngleAnimation.fromValue = [self valueForKey:#"actualEndAngle"];
endAngleAnimation.toValue = [[NSNumber alloc] initWithFloat:newEndAngle];
if (completionBlock) {
[CATransaction setCompletionBlock:completionBlock];
}
if (colorTU && colorTU.CGColor != self.color) {
[self addAnimation:colorAnimation forKey:#"color"];
}
if (newStartAngle != self.actualStartAngle) {
[self addAnimation:startAngleAnimation forKey:#"actualStartAngle"];
}
if (newEndAngle != self.actualEndAngle) {
[self addAnimation:endAngleAnimation forKey:#"actualEndAngle"];
}
[CATransaction commit];
The problem is that I would like to have the image only take up the space that the individual layer takes up (resp. the shape in it). This is solved in general by this method:
- (BOOL)imageFitsIntoSliceWithStartAngle:(float)sA andEndAngle:(float)eA{
BOOL fits = YES;
CGRect rectToUse = [self getFrameForImgLayerWithStartAngle:sA andEndAngle:eA];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:self.center];
[path addArcWithCenter:self.center radius:self.deg startAngle:sA endAngle:eA clockwise:YES];
for (int w = 0; w <= 1; w++) {
if (fits) {
for (int h = 0; h <= 1; h++) {
if (![path containsPoint:CGPointMake(rectToUse.origin.x + rectToUse.size.width * w, rectToUse.origin.y + rectToUse.size.height * h)]) {
fits = NO;
break;
}
}
}
}
return fits;
}
The problem now is that it may fit in the end and not at the beginning of the animation, so I would have to cut the image to be as big as the slice when this happens.
I have tried the following:
set a mask for the slice instead of drawing it directly -> This is firstly bad coding and I have found it to be very energy inefficient, also it created an error (expecting model layer not copy) for me.
Any ideas on how to solve this issue? (not what I tried but this problem in general, I don't think you should do something like what I tried above)
Thanks

Don't know if it'll help, but here the drawing code for a pie chart part :
- (void)drawChartWithFrame:(CGRect)frame startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle fillColor:(UIColor *)fillColor strokeColor:(UIColor *)strokeColor
{
//// Oval Drawing
CGRect ovalRect = CGRectMake(CGRectGetMinX(frame),CGRectGetMinY(frame), CGRectGetWidth(frame), CGRectGetHeight(frame));
UIBezierPath* ovalPath = UIBezierPath.bezierPath;
[ovalPath addArcWithCenter: CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect)) radius: CGRectGetWidth(ovalRect) / 2 startAngle: -(startAngle - 25) * M_PI/180 endAngle: -(endAngle - 93) * M_PI/180 clockwise: YES];
[ovalPath addLineToPoint: CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))];
[ovalPath closePath];ansform ovalTransform = CGAffineTransformMakeTranslation(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect));
ovalTransform = CGAffineTransformScale(ovalTransform, 1, CGRectGetHeight(ovalRect) / CGRectGetWidth(ovalRect));
[ovalPath applyTransform: ovalTransform];
[fillColor setFill];
[ovalPath fill];
[strokeColor setStroke];
ovalPath.lineWidth = 1;
[ovalPath stroke];
}
It's much lighter to animate that a real image.

Related

Animate CAGradientLayer while drawing or fill color

I am working on a circular progress view. I am adding slices or ARCs of a circle with unselected and selected colours that indicate progress of some task.
I am working in CALayers to draw Arcs and add animations. Everything is working fine for all slices with out gradient. If I add gradient in any slice / Arc it does not animate while drawing immediately.
Please help me in this issue to add animation in CAShapeLayer having CAGradientLayer.
Below is my code and my tried approaches...
NOTE: I have tried three approaches to add animation. Please check the commented code also
- (void)drawArcAnimation:(CGRect)rect {
if (self.itemIndex >= self.sliceItems.count) {
return;
}
float total = [self calculateTotal];
SliceItem *item = self.sliceItems[self.itemIndex];
UIBezierPath* path = [UIBezierPath bezierPath];
float angle = (item.itemValue/total) * 2 * M_PI;
float endAngle = self.startAngle + angle;
totalProgress += item.itemValue;
if (item.shouldGradient) {
// ***** This code will be used if we go for second approach of animation ****
UIBezierPath *rectToAnimateFrom = [UIBezierPath bezierPathWithArcCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2) radius:rect.size.width/2 - self.lineWidth startAngle:self.startAngle endAngle:self.startAngle clockwise:NO];
UIBezierPath *rectToAnimateTo = [UIBezierPath bezierPathWithArcCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2) radius:rect.size.width/2 - self.lineWidth startAngle:self.startAngle endAngle:endAngle clockwise:NO];
// ***** END ****
CGMutablePathRef arc = CGPathCreateMutable();
CGPathAddArc(arc, NULL,
(rect.size.width / 2), (rect.size.height / 2),
rect.size.width/2 - self.lineWidth,
self.startAngle,
endAngle,
NO);
self.startAngle = endAngle;
CGFloat lineWidth = self.lineWidth;
CGPathRef strokedArc =
CGPathCreateCopyByStrokingPath(arc, NULL,
lineWidth,
kCGLineCapButt,
kCGLineJoinMiter, // the default
10); // 10 is default miter limit
CAShapeLayer *segment = [CAShapeLayer layer];
segment.fillColor = item.itemColor.CGColor;
segment.strokeColor = [UIColor clearColor].CGColor;
segment.path = strokedArc;
// [self.baseLayer addSublayer:segment];
CAGradientLayer *gradient = [CAGradientLayer layer];
if (totalProgress > 50) {
gradient.colors = #[(id)item.itemSecondaryColor.CGColor, (id)item.itemColor.CGColor,(id)item.itemColor.CGColor,(id)item.itemColor.CGColor];
}else {
gradient.colors = #[(id)item.itemColor.CGColor, (id)item.itemColor.CGColor, (id)item.itemColor.CGColor,(id)item.itemSecondaryColor.CGColor];
}
gradient.frame = CGPathGetBoundingBox(segment.path);
CAShapeLayer *mask = [CAShapeLayer layer];
CGAffineTransform translation = CGAffineTransformMakeTranslation(-CGRectGetMinX(gradient.frame),
-CGRectGetMinY(gradient.frame));
mask.path = CGPathCreateCopyByTransformingPath(segment.path,
&translation);
gradient.mask = mask;
[self.baseLayer addSublayer:gradient];
if (_duration == 0.0) {
self.itemIndex++;
[self drawArcAnimation:rect];
}else {
// ******* First Approach to animate *******
[CATransaction begin];
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = _duration*(angle/(2 * M_PI));
drawAnimation.repeatCount = 1.0;
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:1.0f];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[CATransaction setCompletionBlock:^{
self.itemIndex++;
[self drawArcAnimation:rect];
}];
[gradient addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
[CATransaction commit];
// ******* Second Approach to animate *******
// [CATransaction begin];
// CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
// animation.fromValue = (__bridge id _Nullable)(rectToAnimateFrom.CGPath);
// animation.toValue = (__bridge id _Nullable)(rectToAnimateTo.CGPath);
// animation.duration = 3;
// animation.repeatCount = 1;
// animation.removedOnCompletion = false;
// animation.fillMode = kCAFillModeForwards;
//
// [CATransaction setCompletionBlock:^{
// self.itemIndex++;
// [self drawArcAnimation:rect];
// }];
//
// [gradient addAnimation:animation forKey:#"fill animation"];
// [CATransaction commit];
// ******* Third Approach to animate *******
// NSArray *fromColors = #[(id)UIColor.whiteColor.CGColor, (id)UIColor.clearColor.CGColor];
// NSArray *toColors = gradient.colors;
// [gradient setColors:toColors];
//
// [CATransaction begin];
// CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:#"colors"];
// animation1.fromValue = fromColors;
// animation1.toValue = toColors;
// animation1.duration = _duration;
// animation1.removedOnCompletion = YES;
// animation1.fillMode = kCAFillModeForwards;
// animation1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
// [CATransaction setCompletionBlock:^{
// self.itemIndex++;
// [self drawArcAnimation:rect];
// }];
// [gradient addAnimation:animation1 forKey:#"animateGradient"];
// [CATransaction commit];
}
}else {
[path addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:rect.size.width/2 - self.lineWidth
startAngle:self.startAngle
endAngle:endAngle
clockwise:YES];
self.startAngle = endAngle;
CAShapeLayer *layer = [CAShapeLayer layer];
layer.path = path.CGPath;
CGRect frame = path.bounds;
frame.size.height = rect.size.height;
frame.size.width = frame.size.width * 30;
layer.strokeColor = item.itemColor.CGColor;
layer.fillColor = nil;
layer.lineWidth = self.lineWidth;
layer.strokeStart = 0;
layer.strokeEnd = 1.0;
[self.baseLayer addSublayer:layer];
if (_duration == 0.0) {
self.itemIndex++;
[self drawArcAnimation:rect];
}else {
[CATransaction begin];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.duration = _duration*(angle/(2 * M_PI));
animation.fromValue = #(0.0);
animation.toValue = #(1.0);
[CATransaction setCompletionBlock:^{
self.itemIndex++;
[self drawArcAnimation:rect];
}];
[layer addAnimation:animation forKey:nil];
[CATransaction commit];
}
}
}
In above code you can a condition if (item.shouldGradien) and its else part. Animations are working fine in else part of CALayers but no in gradient part.
What I am doing wrong here? Any help... ???

Draw CAShapeLayer strokeEnd circle one side

I have implemented a basic line drawing animation for a progress bar.The line has a round cap edge.When I moving the slider the line drawing the ending of line is having a rounded cap but in the starting its a square. below is the code I am using am i missing anything??
Image Attached: expected output
Below Code I tried
#interface ViewController ()
{
CGPoint center;
CGFloat radius;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
center = CGPointMake(200.0, 200.0);
self.view.layer.backgroundColor = [[UIColor grayColor] CGColor];
}
- (IBAction)greenSlider:(UISlider *)sender {
[self.view.layer addSublayer:[self drawArcAtCenter:center startAngle:sender.minimumValue endAngle:sender.value radius:100.0 withColor:[UIColor greenColor]]];
}
- (IBAction)blueSlider:(UISlider *)sender {
int value = (int)sender.value;
if (value > 360) {
value = value % 90;
}
[self.view.layer addSublayer:[self drawArcAtCenter:center startAngle:sender.minimumValue endAngle:(float)value radius:100 withColor:[UIColor blueColor]]];
}
- (CAShapeLayer *) drawArcAtCenter:(CGPoint)newCenter startAngle:(CGFloat)sAngle endAngle:(CGFloat)bAngle radius:(CGFloat) radious withColor:(UIColor*) color
{
CGFloat begAngle = sAngle * 3.1459 / 180.0;
CGFloat endAngle = bAngle * 3.1459 / 180.0;
CAShapeLayer *layer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:newCenter radius:radious startAngle:begAngle endAngle:endAngle clockwise:YES];
layer.path = [path CGPath];
layer.strokeColor = [color CGColor];
layer.fillColor = [[UIColor clearColor] CGColor];;
layer.lineWidth = 50.0;
layer.strokeEnd = 50.0f;
layer.lineCap = kCALineCapRound;
layer.cornerRadius = 30.0f;
return layer;
}

Draw Triangle iOS

The code below is drawing me a circle, how can I modify the existing code to draw a triangle instead?
_colorDotLayer = [CALayer layer];
CGFloat width = self.bounds.size.width-6;
_colorDotLayer.bounds = CGRectMake(0, 0, width, width);
_colorDotLayer.allowsGroupOpacity = YES;
_colorDotLayer.backgroundColor = self.annotationColor.CGColor;
_colorDotLayer.cornerRadius = width/2;
_colorDotLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
While there are shown several Core Graphics solution, I want to add a Core Animation based solution.
- (void)viewDidLoad
{
[super viewDidLoad];
UIBezierPath* trianglePath = [UIBezierPath bezierPath];
[trianglePath moveToPoint:CGPointMake(0, view3.frame.size.height-100)];
[trianglePath addLineToPoint:CGPointMake(view3.frame.size.width/2,100)];
[trianglePath addLineToPoint:CGPointMake(view3.frame.size.width, view2.frame.size.height)];
[trianglePath closePath];
CAShapeLayer *triangleMaskLayer = [CAShapeLayer layer];
[triangleMaskLayer setPath:trianglePath.CGPath];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,0, size.width, size.height)];
view.backgroundColor = [UIColor colorWithWhite:.75 alpha:1];
view.layer.mask = triangleMaskLayer;
[self.view addSubview:view];
}
code based on my blog post.
#implementation TriangleView {
CAShapeLayer *_colorDotLayer;
}
- (void)layoutSubviews {
[super layoutSubviews];
if (_colorDotLayer == nil) {
_colorDotLayer = [CAShapeLayer layer];
_colorDotLayer.fillColor = self.annotationColor.CGColor;
[self.layer addSublayer:_colorDotLayer];
}
CGRect bounds = self.bounds;
CGFloat radius = (bounds.size.width - 6) / 2;
CGFloat a = radius * sqrt((CGFloat)3.0) / 2;
CGFloat b = radius / 2;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, -radius)];
[path addLineToPoint:CGPointMake(a, b)];
[path addLineToPoint:CGPointMake(-a, b)];
[path closePath];
[path applyTransform:CGAffineTransformMakeTranslation(CGRectGetMidX(bounds), CGRectGetMidY(bounds))];
_colorDotLayer.path = path.CGPath;
}
- (void)awakeFromNib {
[super awakeFromNib];
self.annotationColor = [UIColor redColor];
}
#end
Result:
I think is more easy than this solutions:
UIBezierPath *path = [UIBezierPath new];
[path moveToPoint:(CGPoint){20, 0}];
[path addLineToPoint:(CGPoint){40, 40}];
[path addLineToPoint:(CGPoint){0, 40}];
[path addLineToPoint:(CGPoint){20, 0}];
// Create a CAShapeLayer with this triangular path
// Same size as the original imageView
CAShapeLayer *mask = [CAShapeLayer new];
mask.frame = self.viewTriangleCallout.bounds;
mask.path = path.CGPath;
This is my white triangle:
You can change points or rotate if you want:
UIBezierPath *path = [UIBezierPath new];
[path moveToPoint:(CGPoint){0, 0}];
[path addLineToPoint:(CGPoint){40, 0}];
[path addLineToPoint:(CGPoint){20, 20}];
[path addLineToPoint:(CGPoint){0, 0}];
// Create a CAShapeLayer with this triangular path
// Same size as the original imageView
CAShapeLayer *mask = [CAShapeLayer new];
mask.frame = self.viewTriangleCallout.bounds;
mask.path = path.CGPath;
Example code, it is based on this SO Answer which draws stars:
#implementation TriangleView
-(void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
int sides = 3;
double size = 100.0;
CGPoint center = CGPointMake(160.0, 100.0);
double radius = size / 2.0;
double theta = 2.0 * M_PI / sides;
CGContextMoveToPoint(context, center.x, center.y-radius);
for (NSUInteger k=1; k<sides; k++) {
float x = radius * sin(k * theta);
float y = radius * cos(k * theta);
CGContextAddLineToPoint(context, center.x+x, center.y-y);
}
CGContextClosePath(context);
CGContextFillPath(context); // Choose for a filled triangle
// CGContextSetLineWidth(context, 2); // Choose for a unfilled triangle
// CGContextStrokePath(context); // Choose for a unfilled triangle
}
#end
Use UIBezeierPaths
CGFloat radius = 20;
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, (center.x + bottomLeft.x) / 2, (center.y + bottomLeft.y) / 2);
CGPathAddArcToPoint(path, NULL, bottomLeft.x, bottomLeft.y, bottomRight.x, bottomRight.y, radius);
CGPathAddArcToPoint(path, NULL, bottomRight.x, bottomRight.y, center.x, center.y, radius);
CGPathAddArcToPoint(path, NULL, center.x, center.y, bottomLeft.x, bottomLeft.y, radius);
CGPathCloseSubpath(path);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath:path];
CGPathRelease(path);

Displaying a UIBeizerPath using a CAShapeLayer vs Quartz 2D doesn't look as nice

I'm writing an app with a circular progress bar, and have recently changed it to be drawn with a couple of CAShapeLayers (one for the white background, one for the purple progress) so I can animate the purple line, rather than making new UIBeizerPaths in the drawRect of a UIView.
Having made this change, I've had some issues with how it looks now. Below is a screenshot of each way, and a diff (using “Diff” an image using ImageMagick)
My main issue with it is that it looks slightly blurry when using the CAShapeLayers - and I can't for the life of me figure out how to make it look sharper. The other issue is some of the white background is showing through the purple, but I can get around that by changing the width of the white line to be slightly less wide.
The drawRect code as it was originally written is as follows:
- (void)drawRect:(CGRect)rect {
UIBezierPath *backCircle = [UIBezierPath bezierPath];
[backCircle addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:(rect.size.width/2.0f) - 4.0f
startAngle:(self.endAngle - self.startAngle) * _percent + self.startAngle
endAngle:self.endAngle
clockwise:YES];
backCircle.lineWidth = 5;
[[UIColor whiteColor] setStroke];
[backCircle stroke];
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:(rect.size.width/2.0f) - 4.0f
startAngle:self.startAngle
endAngle:(self.endAngle - self.startAngle) * _percent + self.startAngle
clockwise:YES];
bezierPath.lineWidth = 5;
[[UIColor purpleColor] setStroke];
[bezierPath stroke];
}
with the init code:
self.startAngle = M_PI * 1.5;
self.endAngle = self.startAngle + (M_PI * 2);
And then with the 2 layers:
- (void)setUpView
{
CGFloat startAngle = M_PI * 1.5;
CGFloat endAngle = startAngle + (M_PI * 2);
UIBezierPath *processPath = [UIBezierPath bezierPath];
[processPath addArcWithCenter:self.boundsCenter
radius:self.radius
startAngle:startAngle
endAngle:endAngle
clockwise:YES];
self.backgroundShapeLayer.path = [processPath CGPath];
self.progressShapeLayer.path = [processPath CGPath];
}
- (CGPoint)boundsCenter
{
return CGPointMake((self.bounds.size.width ) / 2.0, (self.bounds.size.height ) / 2.0);
}
- (CGFloat)radius
{
return (self.bounds.size.width / 2.0) - 4.0f;
}
- (CAShapeLayer *)progressShapeLayer
{
if (_progressShapeLayer == nil) {
_progressShapeLayer = [CAShapeLayer layer];
_progressShapeLayer.fillColor = [[UIColor clearColor] CGColor];
_progressShapeLayer.lineWidth = 5.0;
_progressShapeLayer.strokeStart = 0.0;
_progressShapeLayer.contentsScale = [[UIScreen mainScreen] scale];
[self.layer addSublayer:_progressShapeLayer];
}
return _progressShapeLayer;
}
- (CAShapeLayer *)backgroundShapeLayer
{
if (_backgroundShapeLayer == nil) {
_backgroundShapeLayer = [CAShapeLayer layer];
_backgroundShapeLayer.fillColor = [[UIColor clearColor] CGColor];
_backgroundShapeLayer.strokeColor = [[UIColor whiteColor] CGColor];
_backgroundShapeLayer.lineWidth = 5.0;
_backgroundShapeLayer.strokeStart = 0.0;
_backgroundShapeLayer.contentsScale = [[UIScreen mainScreen] scale];
[self.layer addSublayer:_backgroundShapeLayer];
}
return _backgroundShapeLayer;
}
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated
{
if(isnan(progress) || progress < 0) return;
_progress = progress;
if(!animated) {
[CATransaction setDisableActions:YES];
}
self.progressShapeLayer.strokeColor = [self.progressColour CGColor];
self.progressShapeLayer.strokeEnd = _progress;
}
I really feel like I'm going a bit crazy staring at the zoomed in screen, but it just doesn't look quite right...

How to put 2 layers to follow same bezierpath without one hiding the other

I have created 2 CALayers of same size and am passing these to the method below. However, the two layers runs together. How can I seperate these so that both are visible?
- (void) myAnimation : (CALayer *) sublayer {
UIBezierPath* aPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(30, 100, 270, 270)];
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = aPath.CGPath;
anim.rotationMode = kCAAnimationRotateAuto;
anim.repeatCount = HUGE_VALF;
anim.duration =35.0;
[sublayer addAnimation:anim forKey:#"race"];
}
Your path begins and ends at the same point. Assuming both beginning times are the same and duration is the same, your layers will precisely overlap. You can either shift the beginning time or rotate your UIBezierPath* aPath Here's an example of rotating your UIBezierPath* aPath and altering the duration. It should give you an idea how you could alter duration, begin time, rotation, etc.
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setBackgroundColor:[UIColor blackColor]];
CALayer * layer1 = [CALayer layer];
CALayer * layer2 = [CALayer layer];
[layer1 setFrame:CGRectMake(0, 0, 5, 50)];
[layer2 setFrame:CGRectMake(0, 0, 5, 100)];
layer1.backgroundColor = [[UIColor colorWithRed:.1 green:.5 blue:1 alpha:.35] CGColor];
layer2.backgroundColor = [[UIColor colorWithRed:.9 green:.2 blue:.5 alpha:.35] CGColor];
[self.view.layer addSublayer:layer1];
[self.view.layer addSublayer:layer2];
[self myAnimation:layer1 andRotation:0 andDuration:35.0];
[self myAnimation:layer2 andRotation:10 andDuration:10.0];
}
- (void) myAnimation:(CALayer *)sublayer andRotation:(CGFloat)rot andDuration:(CFTimeInterval)dur {
CGRect rect = CGRectMake(30, 100, 270, 270);
UIBezierPath* aPath = [UIBezierPath bezierPathWithOvalInRect:rect];
// Creating a center point around which we will transform the path
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, center.x, center.y);
transform = CGAffineTransformRotate(transform, rot);
// Recenter the transform
transform = CGAffineTransformTranslate(transform, -center.x, -center.y);
// This is the new path we will use.
CGPathRef rotated = CGPathCreateCopyByTransformingPath(aPath.CGPath, &transform);
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = rotated;
anim.rotationMode = kCAAnimationRotateAuto;
anim.repeatCount = HUGE_VALF;
anim.duration =dur;
[sublayer addAnimation:anim forKey:#"race"];
}

Resources