CAShapeLayer path animation -- shrinking a circle - ios

I'm trying to shrink a circular CAShapeLayer but using the key path 'path' does not seem to be working.
CGPathRef startPath = [UIBezierPath bezierPathWithOvalInRect:startRect].CGPath;
CGPathRef endPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(startRect, 15, 15)].CGPath;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.duration = 1.0;
animation.fromValue = (__bridge id)startPath;
animation.toValue = (__bridge id)endPath;
animation.autoreverses = YES;
animation.repeatCount = CGFLOAT_MAX;
animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.6 :0.3 :0.8 :0.45];
//circleLayer is a CAShapeLayer
[self.circleLayer addAnimation:animation forKey:#"pathAnimation"];
I think i must be misunderstanding about how a path animation works because pretty much identical code seems to work fine if i am try to animate, say, the opacity or the transform.
Any ideas?

I recently had the same question and ended up solving it by creating the circle, then animating the scale CGAffineTransform. In the view whose layer is the circle, I simply used
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformScale(transform, scaleX, scaleY);
[UIView animateWithDuration: 0.5 animations: ^{
self.transform = transform;
}];
Hope this helps!

Related

CABasicAnimation Circular Mask Reveal Scaling Path Animation

I am aiming to reveal a menu with a circular reveal scaling animation. For this I first set the mask to a radius with a radius of 0 at the beginning.
#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)
CAShapeLayer *mask = [CAShapeLayer new];
mask.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(25, 25) radius:0 startAngle:0 endAngle:DEGREES_TO_RADIANS(360) clockwise:YES].CGPath;
self.layer.mask = mask;
Then I animate the new mask with a CABasicAnimation.
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.toValue = (id)[UIBezierPath bezierPathWithArcCenter:CGPointMake(25, 25) radius:self.frame.size.height*1.2 startAngle:0 endAngle:DEGREES_TO_RADIANS(360) clockwise:YES].CGPath;
animation.removedOnCompletion = NO;
animation.duration = 0.45f;
animation.fillMode = kCAFillModeForwards;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[self.layer.mask addAnimation:animation forKey:nil];
On the first look this is working but the problem is that the animation itself is more oval than circular: http://i.stack.imgur.com/fqPYJ.png
The code is inspired by Animate circular mask of UIImageView in iOS.
Your code looks reasonable. (Although if you're always using a full circle you could probably use the simpler bezierPathWithOvalInRect method instead of bezierPathWithArcCenter).
It's hard to tell what I'm looking at in the image you linked. The two layer are white so we can't tell what is what. You might want to set one of the layers to a colored background while you're testing.
As to why you would get an oval: Do you have a transform on any of your layers? Specifically a scale transform? That would cause your distortion.

CABasicAnimation is not triggered after creation of the layer

I have CALayer subclass. After creation of the layer I want to animate affineTransform of layer but without success. New value is set but the change is not animated. If I trigger the animation after 1 second everything works perfect.
RCLClockDialLayer *layer = [RCLClockDialLayer layer];
layer.frame = CGRectMake(center.x, center.y, 2, self.radius);
layer.center = center;
layer.radius = self.radius;
layer.width = width;
layer.backgroundColor = [UIColor blackColor].CGColor;
layer.color = [UIColor whiteColor];
layer.affineTransform = CGAffineTransformRotate(layer.affineTransform, RCLDegreesToRadians(270));
[self addSubLayer:layer];
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformRotate(transform, RCLDegreesToRadians(80));
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"affineTransform"];
animation.duration = .5;
animation.fromValue = [layer valueForKey:#"affineTransform"];
animation.toValue = [NSValue valueWithCGAffineTransform:transform];
animation.fillMode = kCAFillModeForwards;
animation.cumulative = YES;
animation.removedOnCompletion = NO;
layer.affineTransform = transform;
[layer addAnimation:animation forKey:#"affineTransform"];
This is my code. I have tried many different solutions but nothing works. Do someone know what is the reason for this?

iOS Animate Rotation at an Angle

I'm trying to do a 360 spin of a view but at a 45 degree tilt.
Like this
I can't figure out for to do it.
So far I've managed this
CABasicAnimation* animation = [CABasicAnimation
animationWithKeyPath:#"transform.rotation.y"];
animation.fromValue = #(0);
animation.toValue = #(2 * M_PI);
animation.duration = 1;
[self.layer addAnimation:animation forKey:#"rotation"];
Which spins it on it's y axis, but what I want is for this Y axis to be tilted 45 degree before it's spun.
First, you should see the link below which explains that the differences between "frame" and "bounds".
UIView frame, bounds and center
And now, here is my answer.
/* add the 4 lines below */
self.transform = CGAffineTransformMakeRotation(M_PI_4);
self.layer.bounds = CGRectMake(0.0f, 0.0f, 60.0f, 200.0f);
self.layer.position = CGPointMake(150.0f, 300.0f);
CABasicAnimation* animation = [CABasicAnimation
animationWithKeyPath:#"transform.rotation.y"];
animation.fromValue = #(0);
animation.toValue = #(2 * M_PI);
animation.duration = 1;
[self.layer addAnimation:animation forKey:#"rotation"];
try this;
CABasicAnimation* animation = [CABasicAnimation
animationWithKeyPath:#"transform"];
CATransform3D rtfm = CATransform3DMakeRotation(2 * M_PI , 1.0f, 1.0f, 0.0f);
rtfm.m34 = -0.0015f;
animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
animation.toValue = [NSValue valueWithCATransform3D:rtfm];
animation.duration = 1;
[self.layer addAnimation:animation forKey:#"rotation"];
1- Change the anchor point to the upper right edge
self.someView.layer.anchorPoint = CGPointMake(1.0, 0.5);
2- rotate the view by 45 or 35 degrees
CGFloat radians = (M_PI/180) * (-35);
self.someView.transform = CGAffineTransformRotate(self.someView.transform, radians);
3- rotate or flip your view by X axis
CGFloat radians = (M_PI/180) * (180);
[UIView animateWithDuration:1 animations:^{
self.someView.layer.transform = CATransform3DRotate(self.someView.layer.transform,radians , 1, 0.0, 0.0);
} completion:^(BOOL finished) {
if(finished) {
}
}];
Works like a charm :)

CABasicAnimation - transform scale keep in center

Trying to animatie an ellipse masked on a UIView to be scale transformed remaining in the center position.
I have found CALayer - CABasicAnimation not scaling around center/anchorPoint, and followed by adding a bounds property to the maskLayer CAShapeLayer however, this ends with the mask being positioned in the left corner with only 1/4 of it showing. I would like the mask to remain within the center of the screen.
#synthesize maskedView;
- (void)viewDidLoad
{
[super viewDidLoad];
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Next"];
vc.view.frame = CGRectMake(0,0, self.view.frame.size.width, self.view.frame.size.height);
vc.view.layer.bounds = self.view.layer.bounds;
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
CGRect maskRect = CGRectMake((self.view.frame.size.width/2)-50, (self.view.frame.size.height/2)-50, 100, 100);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddEllipseInRect(path, nil, maskRect);
[maskLayer setPath:path];
CGPathRelease(path);
vc.view.layer.mask = maskLayer;
[self.view addSubview:vc.view];
maskedView = vc.view;
[self startAnimation];
}
Animation...
-(void)startAnimation
{
maskedView.layer.mask.bounds = CGRectMake((self.view.frame.size.width/2)-50, (self.view.frame.size.height/2)-50, 100, 100);
maskedView.layer.mask.anchorPoint = CGPointMake(.5,.5);
maskedView.layer.mask.contentsGravity = #"center";
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
animation.duration = 1.0f;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = [NSNumber numberWithFloat:1.0f];
animation.toValue = [NSNumber numberWithFloat:5.0f];
[maskedView.layer.mask addAnimation:animation forKey:#"animateMask"];
}
Update
I seem to have fixed this with a second animation on the position key. Is this correct or is there a better way of doing this?
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:#"position"];
animation2.duration = 1.0f;
animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation2.fromValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
animation2.toValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
[toBeMask.layer.mask addAnimation:animation2 forKey:#"animateMask2"];
You can create a scale around the center of the object instead of (0, 0) like this:
CABasicAnimation *scale = [CABasicAnimation animationWithKeyPath:#"transform"];
CATransform3D tr = CATransform3DIdentity;
tr = CATransform3DTranslate(tr, self.bounds.size.width/2, self.bounds.size.height/2, 0);
tr = CATransform3DScale(tr, 3, 3, 1);
tr = CATransform3DTranslate(tr, -self.bounds.size.width/2, -self.bounds.size.height/2, 0);
scale.toValue = [NSValue valueWithCATransform3D:tr];
So we start out with the "identity" transform (which means 'no transform'). Then we translate (move) to the center of the object. Then we scale. Then we move back to origo so we're back where we started (we only wanted to affect the scale operation).
I seem to have fixed this with a second animation on the position key. Is this correct or is there a better way of doing this?
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:#"position"];
animation2.duration = 1.0f;
animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation2.fromValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
animation2.toValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
[toBeMask.layer.mask addAnimation:animation2 forKey:#"animateMask2"];
I am not sure why, but CAShapeLayer does not set the bounds property to the layer. In my case, that's was the problem.
Try setting the bounds. That should work.
I did something like this for building the initial circle
self.circle = [CAShapeLayer layer];
CGRect roundedRect = CGRectMake(0, 0, width, width);
self.circle.bounds = roundedRect;
UIBezierPath *start = [UIBezierPath bezierPathWithRoundedRect:roundedRect cornerRadius:width/2];
[self.circle setPath:start.CGPath];
self.circle.position = CGPointMake(whatever suits you);
self.circleView.layer.mask = self.circle;
[self.circleView.layer.mask setValue: #(1) forKeyPath: #"transform.scale"];
And something like this for scaling it
CABasicAnimation *scale = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scale.fromValue = [self.circleView.layer.mask valueForKeyPath:#"transform.scale"];
scale.toValue = #(100);
scale.duration = 1.0;
scale.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.circleView.layer.mask setValue:scale.toValue forKeyPath:scale.keyPath];
[self.circleView.layer.mask addAnimation:scale forKey:scale.keyPath];
PD: AnchorPoint is by default (.5,.5) according to apple documentations...but we all know that does not mean anything right?
Hope it helps!!
I have written following function with many added tweaks and options, could be useful for many others.
func layerScaleAnimation(layer: CALayer, duration: CFTimeInterval, fromValue: CGFloat, toValue: CGFloat) {
let timing = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
let scaleAnimation: CABasicAnimation = CABasicAnimation(keyPath: "transform.scale")
CATransaction.begin()
CATransaction.setAnimationTimingFunction(timing)
scaleAnimation.duration = duration
scaleAnimation.fromValue = fromValue
scaleAnimation.toValue = toValue
layer.add(scaleAnimation, forKey: "scale")
CATransaction.commit()
}
Note: Don't forget to update size after animation completion, because CATransaction reverts back to actual size.

My CALayer transform holds after animation, but the perspective disappears

I have the following code that rotates a CALayer by -45degrees on the Y axis:
#define D2R(x) (x * (M_PI/180.0))
- (void) swipe:(UISwipeGestureRecognizer *)recognizer
{
CATransform3D transform = CATransform3DMakeRotation(D2R(-45), 0, 1.0, 0);
transform.m34 = -1.0 / 850;
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath: #"transform"];
transformAnimation.fillMode = kCAFillModeForwards;
transformAnimation.removedOnCompletion = NO;
transformAnimation.toValue = [NSValue valueWithCATransform3D:transform];
transformAnimation.duration = 0.5;
[self.layer addAnimation:transformAnimation forKey:#"transform"];
}
The animation works, except it ends with no perspective - ignoring my m34 setting if I'm understanding things correctly.
Halfway through:
At the end:
What am I doing wrong?
Animation only affects the appearance of the view during the animation. It doesn't get applied to the view after the animation ends. You need to do this yourself. I'm guessing something like this right after adding the animation will work:
self.layer.transform = transform;
You can do this right away, as the animation will hide it until the animation completes.
Try this :
- (void) swipe:(UISwipeGestureRecognizer *)recognizer
{
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -10 / 850.0;
transform = CATransform3DRotate(transform, D2R(-45), 0, 1.0, 0);
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath: #"transform"];
transformAnimation.fillMode = kCAFillModeForwards;
transformAnimation.removedOnCompletion = NO;
transformAnimation.toValue = [NSValue valueWithCATransform3D:transform];
transformAnimation.duration = 0.5;
[self.layer addAnimation:transformAnimation forKey:#"transform"];
}
And end the effect is like this :

Resources