How to animate CAShapeLayer path and fillColor - iphone

How to animate CAShapeLayer path and fillColor?
How to animate strokeEnd to show the path being drawn? and how to animate fillColor?

To animate path
//setup the CAShapeLayer
myShapeLayer.strokeColor = [[UIColor blueColor] CGColor];
myShapeLayer.lineWidth = 5;
myShapeLayer.fillColor = [[UIColor yellowColor] CGColor];
//Display it!
[self.view.layer addSublayer:myShapeLayer];
//Animate path
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 1.5f;
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
pathAnimation.repeatCount = 10;
pathAnimation.autoreverses = YES;
[myShapeLayer addAnimation:pathAnimation forKey:#"strokeEnd"];
//Animate colorFill
CABasicAnimation *fillColorAnimation = [CABasicAnimation animationWithKeyPath:#"fillColor"];
fillColorAnimation.duration = 1.5f;
fillColorAnimation.fromValue = (id)[[UIColor clearColor] CGColor];
fillColorAnimation.toValue = (id)[[UIColor yellowColor] CGColor];
fillColorAnimation.repeatCount = 10;
fillColorAnimation.autoreverses = YES;
[myShapeLayer addAnimation:fillColorAnimation forKey:#"fillColor"];
I hope this helps :)
2021 Update
Swift
myShapeLayer.strokeColor = UIColor.blue.cgColor
myShapeLayer.lineWidth = 5
myShapeLayer.fillColor = UIColor.yellow.cgColor
//Display it!
view.layer.addSublayer(myShapeLayer)
//Animate path
let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
pathAnimation.duration = 1.5
pathAnimation.fromValue = NSNumber(value: 0.0)
pathAnimation.toValue = NSNumber(value: 1.0)
pathAnimation.repeatCount = 10
pathAnimation.autoreverses = true
myShapeLayer.add(pathAnimation, forKey: "strokeEnd")
//Animate colorFill
let fillColorAnimation = CABasicAnimation(keyPath: "fillColor")
fillColorAnimation.duration = 1.5
fillColorAnimation.fromValue = UIColor.clear.cgColor
fillColorAnimation.toValue = UIColor.yellow.cgColor
fillColorAnimation.repeatCount = 10
fillColorAnimation.autoreverses = true
myShapeLayer.add(fillColorAnimation, forKey: "fillColor")

Related

How to draw line with animation in iPhone

I am drawing line in iphone using CGContextRef. Can any one suggest me how i draw line with animation in iphone.
Please suggest.
Here is the answer (found here : http://soulwithmobiletechnology.blogspot.fr/2012/07/how-to-animate-line-draw.html)
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50.0,0.0)];
[path addLineToPoint:CGPointMake(120.0, 600.0)];
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.frame = self.view.bounds;
pathLayer.path = path.CGPath;
pathLayer.strokeColor = [[UIColor redColor] CGColor];
pathLayer.fillColor = nil;
pathLayer.lineWidth = 2.0f;
pathLayer.lineJoin = kCALineJoinBevel;
[self.view.layer addSublayer:pathLayer];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 2.0;
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
[pathLayer addAnimation:pathAnimation forKey:#"strokeEnd"];
Do not forget to #import <QuartzCore/QuartzCore.h>.
Is it a straight line?.. You can use a 1 pixel wide UIView and animate its frame property. Using [UIView beginAnimations] and [UIView commitAnimations]; Otherwise, see this post.
Edited response
Using core animation, you can do something like this (e.g. behind a button)
CABasicAnimation *theAnimation = [self pathAnimation];
theAnimation.duration=3.0;
theAnimation.autoreverses=YES;
[[self layer] addAnimation:theAnimation forKey:#"animatePath"];
Path animation is defined as:
- (CAAnimation*)pathAnimation;
{
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path,NULL,50.0,120.0);
CGPathAddCurveToPoint(path,NULL,50.0,275.0,
150.0,275.0,
150.0,120.0);
CGPathAddCurveToPoint(path,NULL,150.0,275.0,
250.0,275.0,
250.0,120.0);
CGPathAddCurveToPoint(path,NULL,250.0,275.0,
350.0,275.0,
350.0,120.0);
CGPathAddCurveToPoint(path,NULL,350.0,275.0,
450.0,275.0,
450.0,120.0);
CAKeyframeAnimation *
animation = [CAKeyframeAnimation
animationWithKeyPath:#"position"];
[animation setPath:path];
[animation setDuration:3.0];
[animation setAutoreverses:YES];
CFRelease(path);
return animation;
}
This is only a pointer to what you can do with Core Animation. See this article for detail.
The Swift 3 version of Martin's answer (checked with Xcode 8.3.3)
let path = UIBezierPath()
path.move(to: CGPoint(x: 50, y: 0))
path.addLine(to: CGPoint(x: 120, y: 600))
let pathLayer = CAShapeLayer()
pathLayer.frame = view.bounds
pathLayer.path = path.cgPath
pathLayer.strokeColor = UIColor.red.cgColor
pathLayer.fillColor = nil
pathLayer.lineWidth = 2
pathLayer.lineJoin = kCALineJoinBevel
view.layer.addSublayer(pathLayer)
let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
pathAnimation.duration = 2.0
pathAnimation.fromValue = 0
pathAnimation.toValue = 1
pathLayer.add(pathAnimation, forKey: "strokeEnd")

iPhone shine animation

I'm trying to get my view to do a nice shining animation to catch the user's eyes. Any ideas how to implement this?
Here's what I have so far:
[UIView beginAnimations:#"viewShine" context:self.view];
[UIView setAnimationRepeatAutoreverses:NO];
[UIView setAnimationRepeatCount:0];
[UIView setAnimationDuration:2];
[UIView setAnimationDelegate:self];
//Do nice shining animation here
[UIView commitAnimations];
By shine I mean something like what happens to the "slide to unlock" text when you open the iPhone, or anything that's easy to do and looks nice.
I wrote a method that can be called on any UIView to give it the shine you are looking for without needing to include an image:
-(void)AddShineAnimationToView:(UIView*)aView
{
CAGradientLayer *gradient = [CAGradientLayer layer];
[gradient setStartPoint:CGPointMake(0, 0)];
[gradient setEndPoint:CGPointMake(1, 0)];
gradient.frame = CGRectMake(0, 0, aView.bounds.size.width*3, aView.bounds.size.height);
float lowerAlpha = 0.78;
gradient.colors = [NSArray arrayWithObjects:
(id)[[UIColor colorWithWhite:1 alpha:lowerAlpha] CGColor],
(id)[[UIColor colorWithWhite:1 alpha:lowerAlpha] CGColor],
(id)[[UIColor colorWithWhite:1 alpha:1.0] CGColor],
(id)[[UIColor colorWithWhite:1 alpha:1.0] CGColor],
(id)[[UIColor colorWithWhite:1 alpha:1.0] CGColor],
(id)[[UIColor colorWithWhite:1 alpha:lowerAlpha] CGColor],
(id)[[UIColor colorWithWhite:1 alpha:lowerAlpha] CGColor],
nil];
gradient.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.4],
[NSNumber numberWithFloat:0.45],
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:0.55],
[NSNumber numberWithFloat:0.6],
[NSNumber numberWithFloat:1.0],
nil];
CABasicAnimation *theAnimation;
theAnimation=[CABasicAnimation animationWithKeyPath:#"transform.translation.x"];
theAnimation.duration = 2;
theAnimation.repeatCount = INFINITY;
theAnimation.autoreverses = NO;
theAnimation.removedOnCompletion = NO;
theAnimation.fillMode = kCAFillModeForwards;
theAnimation.fromValue=[NSNumber numberWithFloat:-aView.frame.size.width*2];
theAnimation.toValue=[NSNumber numberWithFloat:0];
[gradient addAnimation:theAnimation forKey:#"animateLayer"];
aView.layer.mask = gradient;
}
Figured it out.
Here's my code in case you want to do something similar:
UIView *whiteView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[whiteView setBackgroundColor:[UIColor whiteColor]];
[whiteView setUserInteractionEnabled:NO];
[self.view addSubview:whiteView];
CALayer *maskLayer = [CALayer layer];
// Mask image ends with 0.15 opacity on both sides. Set the background color of the layer
// to the same value so the layer can extend the mask image.
maskLayer.backgroundColor = [[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.0f] CGColor];
maskLayer.contents = (id)[[UIImage imageNamed:#"ShineMask.png"] CGImage];
// Center the mask image on twice the width of the text layer, so it starts to the left
// of the text layer and moves to its right when we translate it by width.
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.frame = CGRectMake(-whiteView.frame.size.width,
0.0f,
whiteView.frame.size.width * 2,
whiteView.frame.size.height);
// Animate the mask layer's horizontal position
CABasicAnimation *maskAnim = [CABasicAnimation animationWithKeyPath:#"position.x"];
maskAnim.byValue = [NSNumber numberWithFloat:self.view.frame.size.width * 9];
maskAnim.repeatCount = HUGE_VALF;
maskAnim.duration = 3.0f;
[maskLayer addAnimation:maskAnim forKey:#"shineAnim"];
whiteView.layer.mask = maskLayer;
Using this image:
Here is the swift code for the shine effect
class Animate {
/// Add a persistent shimmer animation. Usage: `Animate.shimmer(myView)`
static func shimmer(view: UIView) {
let gradient = CAGradientLayer()
gradient.startPoint = CGPointMake(0, 0)
gradient.endPoint = CGPointMake(1, -0.02)
gradient.frame = CGRectMake(0, 0, view.bounds.size.width*3, view.bounds.size.height)
let lowerAlpha: CGFloat = 0.7
let solid = UIColor(white: 1, alpha: 1).CGColor
let clear = UIColor(white: 1, alpha: lowerAlpha).CGColor
gradient.colors = [ solid, solid, clear, clear, solid, solid ]
gradient.locations = [ 0, 0.3, 0.45, 0.55, 0.7, 1 ]
let theAnimation : CABasicAnimation = CABasicAnimation(keyPath: "transform.translation.x")
theAnimation.duration = 4
theAnimation.repeatCount = Float.infinity
theAnimation.autoreverses = false
theAnimation.removedOnCompletion = false
theAnimation.fillMode = kCAFillModeForwards
theAnimation.fromValue = -view.frame.size.width * 2
theAnimation.toValue = 0
gradient.addAnimation(theAnimation, forKey: "animateLayer")
view.layer.mask = gradient
}
}
Swift 3.0
func shimmer(view: UIView) {
let gradient = CAGradientLayer()
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 1, y: -0.02)
gradient.frame = CGRect(x: 0, y: 0, width: view.bounds.size.width*3, height: view.bounds.size.height)
let lowerAlpha: CGFloat = 0.7
let solid = UIColor(white: 1, alpha: 1).cgColor
let clear = UIColor(white: 1, alpha: lowerAlpha).cgColor
gradient.colors = [ solid, solid, clear, clear, solid, solid ]
gradient.locations = [ 0, 0.3, 0.45, 0.55, 0.7, 1 ]
let theAnimation : CABasicAnimation = CABasicAnimation(keyPath: "transform.translation.x")
theAnimation.duration = 2
theAnimation.repeatCount = Float.infinity
theAnimation.autoreverses = false
theAnimation.isRemovedOnCompletion = false
theAnimation.fillMode = kCAFillModeForwards
theAnimation.fromValue = -view.frame.size.width * 2
theAnimation.toValue = 0
gradient.add(theAnimation, forKey: "animateLayer")
view.layer.mask = gradient
}
It might be worth to mention: in case of shining progress label, like in facebook paper app, or iOS lock screen, you might consider using https://github.com/facebook/Shimmer.
Swift 5.0
func shimmer(){
let gradient = CAGradientLayer()
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 1, y: -0.02)
gradient.frame = CGRect(x: 0, y: 0, width: self.bounds.size.width*3, height: self.bounds.size.height)
let lowerAlpha: CGFloat = 0.8
let solid = UIColor(white: 1, alpha: 1).cgColor
//let verdeVXM = UIColor(red:0.77, green:0.84, blue:0.00, alpha:1.0)
let clear = UIColor(white: 1, alpha: lowerAlpha).cgColor
gradient.colors = [ solid, solid, clear, clear, solid, solid ]
gradient.locations = [ 0, 0.3, 0.45, 0.55, 0.7, 1 ]
let theAnimation : CABasicAnimation = CABasicAnimation(keyPath: "transform.translation.x")
theAnimation.duration = 2
theAnimation.repeatCount = Float.infinity
theAnimation.autoreverses = false
theAnimation.isRemovedOnCompletion = false
theAnimation.fillMode = CAMediaTimingFillMode.forwards
theAnimation.fromValue = -self.frame.size.width * 2
theAnimation.toValue = 0
gradient.add(theAnimation, forKey: "animateLayer")
self.layer.mask = gradient
}

iPhone UIImageView and CABasicAnimation

I am doing a simple card rotation which works fine, but when I add a custom image background, after the card roation, the image is half chopped off. Any ideas?
float scaleValue = M_PI/2;
CABasicAnimation *yRotation;
yRotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
yRotation.fromValue = [NSNumber numberWithFloat:0.0];
yRotation.toValue = [NSNumber numberWithFloat:scaleValue];
yRotation.duration = .5;
CABasicAnimation *yRotation2;
yRotation2 = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
yRotation2.fromValue = [NSNumber numberWithFloat:scaleValue];
yRotation2.toValue = [NSNumber numberWithFloat:0.0];
yRotation2.duration = .5;
yRotation2.cumulative = YES;
yRotation2.beginTime = .5;
CAAnimationGroup *groupAnim = [CAAnimationGroup animation];
groupAnim.removedOnCompletion = NO;
groupAnim.duration = 1;
groupAnim.fillMode = kCAFillModeForwards;
groupAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
groupAnim.animations = [NSArray arrayWithObjects:yRotation, yRotation2, nil];
[[image objectAtIndex:num] addAnimation:groupAnim forKey:#"animateCard"];
[self.view bringSubviewToFront:[image objectAtIndex:num]];
This code is help to you .......
once try it will be perfectly working...without choppedoff
- (void)loadView {
[super loadView];
self.view.backgroundColor = [UIColor blackColor];
// Orbit #1
CALayer *orbit1 = [CALayer layer];
orbit1.bounds = CGRectMake(0, 0, 200, 200);
orbit1.position = self.view.center;
orbit1.cornerRadius = 100;
orbit1.borderColor = [UIColor redColor].CGColor;
orbit1.borderWidth = 1.5;
CALayer *planet1 = [CALayer layer];
planet1.bounds = CGRectMake(0, 0, 20, 20);
planet1.position = CGPointMake(100, 0);
planet1.cornerRadius = 10;
planet1.backgroundColor = [UIColor redColor].CGColor;
[orbit1 addSublayer:planet1];
CABasicAnimation *anim1 = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
anim1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim1.fromValue = [NSNumber numberWithFloat:0];
anim1.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
anim1.repeatCount = HUGE_VALF;
anim1.duration = 8.0;
[orbit1 addAnimation:anim1 forKey:#"transform"];
[self.view.layer addSublayer:orbit1];
// Orbit #2
CALayer *orbit2 = [CALayer layer];
orbit2.bounds = CGRectMake(0, 0, 120, 120);
orbit2.position = planet1.position;
orbit2.cornerRadius = 60;
orbit2.borderColor = [UIColor blueColor].CGColor;
orbit2.borderWidth = 1.5;
CALayer *planet2 = [CALayer layer];
planet2.bounds = CGRectMake(0, 0, 16, 16);
planet2.position = CGPointMake(60, 0);
planet2.cornerRadius = 8;
planet2.backgroundColor = [UIColor blueColor].CGColor;
[orbit2 addSublayer:planet2];
CABasicAnimation *anim2 = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
anim2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim2.fromValue = [NSNumber numberWithFloat:0];
anim2.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
anim2.repeatCount = HUGE_VALF;
anim2.duration = 4.0;
[orbit2 addAnimation:anim2 forKey:#"transform"];
[orbit1 addSublayer:orbit2];
// Orbit #3
CALayer *orbit3 = [CALayer layer];
orbit3.bounds = CGRectMake(0, 0, 72, 72);
orbit3.position = planet2.position;
orbit3.cornerRadius = 36;
orbit3.borderColor = [UIColor grayColor].CGColor;
orbit3.borderWidth = 1.5;
CALayer *planet3 = [CALayer layer];
planet3.bounds = CGRectMake(0, 0, 12, 12);
planet3.position = CGPointMake(36, 0);
planet3.cornerRadius = 6;
planet3.backgroundColor = [UIColor grayColor].CGColor;
[orbit3 addSublayer:planet3];
CABasicAnimation *anim3 = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
anim3.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim3.fromValue = [NSNumber numberWithFloat:0];
anim3.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
anim3.repeatCount = HUGE_VALF;
anim3.duration = 2.0;
[orbit3 addAnimation:anim3 forKey:#"transform"];
[orbit2 addSublayer:orbit3];

Origami transition using CATransform3D perspective

I'm trying to achieve a kind of origami transition on two UIView using only layer capabilities. The idea is to fold two views with a perspective effect. Both views have a perspective, the transition is defined by a rotation on each view, as well as a translation on one view such that this view seems to be attached to the other one.
The issue is that the view overlaps one another in the middle of the transition. I don't want to use a zPosition to visually avoid this overlapping, I really want these two views to act as if they were bound together by their shared side. Here is the code for the transition.
Any idea, or any other solution?
- (void)animateWithPerspective
{
CGFloat rotationAngle = 90;
CATransform3D transform = CATransform3DIdentity;
UIView *topView;
UIView *bottomView;
UIView *mainView;
CGRect frame;
CGFloat size = 200;
mainView = [[UIView alloc] initWithFrame:CGRectMake(10,10, size, size*2)];
[self.view addSubview:mainView];
bottomView = [[UIView alloc] initWithFrame:CGRectZero];
bottomView.layer.anchorPoint = CGPointMake(0.5, 1);
bottomView.frame = CGRectMake(0, size, size, size);
bottomView.backgroundColor = [UIColor blueColor];
[mainView addSubview:bottomView];
topView = [[UIView alloc] initWithFrame:CGRectZero];
topView.layer.anchorPoint = CGPointMake(0.5, 0);
topView.frame = CGRectMake(0, 0, size, size);
topView.backgroundColor = [UIColor redColor];
[mainView addSubview:topView];
transform.m34 = 1.0/700.0;
topView.layer.transform = transform;
bottomView.layer.transform = transform;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:2];
[UIView setAnimationRepeatAutoreverses:YES];
[UIView setAnimationRepeatCount:INFINITY];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
frame = bottomView.frame;
frame.origin.y = bottomView.frame.origin.y - bottomView.frame.size.height - topView.frame.size.height;
bottomView.frame = frame;
topView.layer.transform = CATransform3DRotate(transform, rotationAngle * M_PI/180, 1, 0, 0);
bottomView.layer.transform = CATransform3DRotate(transform, -rotationAngle * M_PI/180, 1, 0, 0);
[UIView commitAnimations];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self animate];
}
To simplify the problem, let's get rid of any perspective transform. Here is a simpler code with the same kind of issue:
- (void)animateWithoutPerspective
{
CGFloat rotationAngle = 90;
UIView *topView;
UIView *bottomView;
UIView *mainView;
CGRect frame;
CGFloat size = 200;
mainView = [[UIView alloc] initWithFrame:CGRectMake(10,10, size, size*2)];
[self.view addSubview:mainView];
bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, size, size, size)];
bottomView.backgroundColor = [UIColor blueColor];
[mainView addSubview:bottomView];
topView = [[UIView alloc] initWithFrame:CGRectZero];
topView.layer.anchorPoint = CGPointMake(0.5, 0);
topView.frame = CGRectMake(10, 0, size-20, size);
topView.backgroundColor = [UIColor redColor];
[mainView addSubview:topView];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:2];
[UIView setAnimationRepeatAutoreverses:YES];
[UIView setAnimationRepeatCount:INFINITY];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
frame = bottomView.frame;
frame.origin.y = bottomView.frame.origin.y - bottomView.frame.size.height;
bottomView.frame = frame;
topView.layer.transform = CATransform3DMakeRotation(rotationAngle * M_PI/180, 1, 0, 0);
[UIView commitAnimations];
}
Finally, here is some solution for a three-sleeves animation with simple shadows added. The key to solve this kind of animation is to use several well organized sublayers and also some CATransformLayer.
- (void)animate
{
CATransform3D transform = CATransform3DIdentity;
CALayer *topSleeve;
CALayer *middleSleeve;
CALayer *bottomSleeve;
CALayer *topShadow;
CALayer *middleShadow;
UIView *mainView;
CGFloat width = 300;
CGFloat height = 150;
CALayer *firstJointLayer;
CALayer *secondJointLayer;
CALayer *perspectiveLayer;
mainView = [[UIView alloc] initWithFrame:CGRectMake(50, 50, width, height*3)];
mainView.backgroundColor = [UIColor yellowColor];
[self.view addSubview:mainView];
perspectiveLayer = [CALayer layer];
perspectiveLayer.frame = CGRectMake(0, 0, width, height*2);
[mainView.layer addSublayer:perspectiveLayer];
firstJointLayer = [CATransformLayer layer];
firstJointLayer.frame = mainView.bounds;
[perspectiveLayer addSublayer:firstJointLayer];
topSleeve = [CALayer layer];
topSleeve.frame = CGRectMake(0, 0, width, height);
topSleeve.anchorPoint = CGPointMake(0.5, 0);
topSleeve.backgroundColor = [UIColor redColor].CGColor;
topSleeve.position = CGPointMake(width/2, 0);
[firstJointLayer addSublayer:topSleeve];
topSleeve.masksToBounds = YES;
secondJointLayer = [CATransformLayer layer];
secondJointLayer.frame = mainView.bounds;
secondJointLayer.frame = CGRectMake(0, 0, width, height*2);
secondJointLayer.anchorPoint = CGPointMake(0.5, 0);
secondJointLayer.position = CGPointMake(width/2, height);
[firstJointLayer addSublayer:secondJointLayer];
middleSleeve = [CALayer layer];
middleSleeve.frame = CGRectMake(0, 0, width, height);
middleSleeve.anchorPoint = CGPointMake(0.5, 0);
middleSleeve.backgroundColor = [UIColor blueColor].CGColor;
middleSleeve.position = CGPointMake(width/2, 0);
[secondJointLayer addSublayer:middleSleeve];
middleSleeve.masksToBounds = YES;
bottomSleeve = [CALayer layer];
bottomSleeve.frame = CGRectMake(0, height, width, height);
bottomSleeve.anchorPoint = CGPointMake(0.5, 0);
bottomSleeve.backgroundColor = [UIColor grayColor].CGColor;
bottomSleeve.position = CGPointMake(width/2, height);
[secondJointLayer addSublayer:bottomSleeve];
firstJointLayer.anchorPoint = CGPointMake(0.5, 0);
firstJointLayer.position = CGPointMake(width/2, 0);
topShadow = [CALayer layer];
[topSleeve addSublayer:topShadow];
topShadow.frame = topSleeve.bounds;
topShadow.backgroundColor = [UIColor blackColor].CGColor;
topShadow.opacity = 0;
middleShadow = [CALayer layer];
[middleSleeve addSublayer:middleShadow];
middleShadow.frame = middleSleeve.bounds;
middleShadow.backgroundColor = [UIColor blackColor].CGColor;
middleShadow.opacity = 0;
transform.m34 = -1.0/700.0;
perspectiveLayer.sublayerTransform = transform;
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.x"];
[animation setDuration:2];
[animation setAutoreverses:YES];
[animation setRepeatCount:INFINITY];
[animation setFromValue:[NSNumber numberWithDouble:0]];
[animation setToValue:[NSNumber numberWithDouble:-90*M_PI/180]];
[firstJointLayer addAnimation:animation forKey:nil];
animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.x"];
[animation setDuration:2];
[animation setAutoreverses:YES];
[animation setRepeatCount:INFINITY];
[animation setFromValue:[NSNumber numberWithDouble:0]];
[animation setToValue:[NSNumber numberWithDouble:180*M_PI/180]];
[secondJointLayer addAnimation:animation forKey:nil];
animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.x"];
[animation setDuration:2];
[animation setAutoreverses:YES];
[animation setRepeatCount:INFINITY];
[animation setFromValue:[NSNumber numberWithDouble:0]];
[animation setToValue:[NSNumber numberWithDouble:-90*M_PI/180]];
[bottomSleeve addAnimation:animation forKey:nil];
animation = [CABasicAnimation animationWithKeyPath:#"bounds.size.height"];
[animation setDuration:2];
[animation setAutoreverses:YES];
[animation setRepeatCount:INFINITY];
[animation setFromValue:[NSNumber numberWithDouble:perspectiveLayer.bounds.size.height]];
[animation setToValue:[NSNumber numberWithDouble:0]];
[perspectiveLayer addAnimation:animation forKey:nil];
animation = [CABasicAnimation animationWithKeyPath:#"position.y"];
[animation setDuration:2];
[animation setAutoreverses:YES];
[animation setRepeatCount:INFINITY];
[animation setFromValue:[NSNumber numberWithDouble:perspectiveLayer.position.y]];
[animation setToValue:[NSNumber numberWithDouble:0]];
[perspectiveLayer addAnimation:animation forKey:nil];
animation = [CABasicAnimation animationWithKeyPath:#"opacity"];
[animation setDuration:2];
[animation setAutoreverses:YES];
[animation setRepeatCount:INFINITY];
[animation setFromValue:[NSNumber numberWithDouble:0]];
[animation setToValue:[NSNumber numberWithDouble:0.5]];
[topShadow addAnimation:animation forKey:nil];
animation = [CABasicAnimation animationWithKeyPath:#"opacity"];
[animation setDuration:2];
[animation setAutoreverses:YES];
[animation setRepeatCount:INFINITY];
[animation setFromValue:[NSNumber numberWithDouble:0]];
[animation setToValue:[NSNumber numberWithDouble:0.5]];
[middleShadow addAnimation:animation forKey:nil];
}
Swift version of Phil's answer
func animate() {
var transform:CATransform3D = CATransform3DIdentity;
var topSleeve:CALayer
var middleSleeve:CALayer
var bottomSleeve:CALayer
var topShadow:CALayer
var middleShadow:CALayer
var mainView:UIView
var width:CGFloat = 300
var height:CGFloat = 150
var firstJointLayer:CALayer
var secondJointLayer:CALayer
var perspectiveLayer:CALayer
mainView = UIView(frame:CGRectMake(50, 50, width, height*3))
mainView.backgroundColor = UIColor.yellowColor()
view.addSubview(mainView)
perspectiveLayer = CALayer()
perspectiveLayer.frame = CGRectMake(0, 0, width, height*2)
mainView.layer.addSublayer(perspectiveLayer)
firstJointLayer = CATransformLayer()
firstJointLayer.frame = mainView.bounds;
perspectiveLayer.addSublayer(firstJointLayer)
topSleeve = CALayer()
topSleeve.frame = CGRectMake(0, 0, width, height);
topSleeve.anchorPoint = CGPointMake(0.5, 0)
topSleeve.backgroundColor = UIColor.redColor().CGColor;
topSleeve.position = CGPointMake(width/2, 0)
firstJointLayer.addSublayer(topSleeve)
topSleeve.masksToBounds = true
secondJointLayer = CATransformLayer()
secondJointLayer.frame = mainView.bounds;
secondJointLayer.frame = CGRectMake(0, 0, width, height*2)
secondJointLayer.anchorPoint = CGPointMake(0.5, 0)
secondJointLayer.position = CGPointMake(width/2, height)
firstJointLayer.addSublayer(secondJointLayer)
middleSleeve = CALayer()
middleSleeve.frame = CGRectMake(0, 0, width, height);
middleSleeve.anchorPoint = CGPointMake(0.5, 0)
middleSleeve.backgroundColor = UIColor.blueColor().CGColor
middleSleeve.position = CGPointMake(width/2, 0)
secondJointLayer.addSublayer(middleSleeve)
middleSleeve.masksToBounds = true
bottomSleeve = CALayer()
bottomSleeve.frame = CGRectMake(0, height, width, height)
bottomSleeve.anchorPoint = CGPointMake(0.5, 0)
bottomSleeve.backgroundColor = UIColor.grayColor().CGColor
bottomSleeve.position = CGPointMake(width/2, height)
secondJointLayer.addSublayer(bottomSleeve)
firstJointLayer.anchorPoint = CGPointMake(0.5, 0)
firstJointLayer.position = CGPointMake(width/2, 0)
topShadow = CALayer()
topSleeve.addSublayer(topShadow)
topShadow.frame = topSleeve.bounds
topShadow.backgroundColor = UIColor.blackColor().CGColor
topShadow.opacity = 0
middleShadow = CALayer()
middleSleeve.addSublayer(middleShadow)
middleShadow.frame = middleSleeve.bounds
middleShadow.backgroundColor = UIColor.blackColor().CGColor
middleShadow.opacity = 0
transform.m34 = -1/700
perspectiveLayer.sublayerTransform = transform;
var animation = CABasicAnimation(keyPath: "transform.rotation.x")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = 0
animation.toValue = -90*M_PI/180
firstJointLayer.addAnimation(animation, forKey: nil)
animation = CABasicAnimation(keyPath: "transform.rotation.x")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = 0
animation.toValue = 180*M_PI/180
secondJointLayer.addAnimation(animation, forKey: nil)
animation = CABasicAnimation(keyPath: "transform.rotation.x")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = 0
animation.toValue = -90*M_PI/180
bottomSleeve.addAnimation(animation, forKey: nil)
animation = CABasicAnimation(keyPath: "bounds.size.height")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = perspectiveLayer.bounds.size.height
animation.toValue = 0
perspectiveLayer.addAnimation(animation, forKey: nil)
animation = CABasicAnimation(keyPath: "position.y")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = perspectiveLayer.position.y
animation.toValue = 0
perspectiveLayer.addAnimation(animation, forKey: nil)
animation = CABasicAnimation(keyPath: "opacity")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = 0
animation.toValue = 0.5
topShadow.addAnimation(animation, forKey: nil)
animation = CABasicAnimation(keyPath: "opacity")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = 0
animation.toValue = 0.5
middleShadow.addAnimation(animation, forKey: nil)
}
At first I though that the linear transformation of the Y position would not imply a linear transformation of the rotation, but it seems that it is the case.
The error is very simple, the perspective value is wrong, the perspective is modeled by positioning an observatory on the Z axis at a negative distance. then you need to negate the perspective value :
transform.m34 = 1.0/(-700.0);
And it does work like expected.
Just for the record, the transformation is not linear for the angles. but the artifact are hidden by the zbuffer.
At mid path the angle would be 60 degree but with the linear animation we get 45 degree. But looking from the right side, from negative Z axis position, the buffer hide the planes intersection.
To illustrate the answers.
I did not put all the animations and the perspective projection (the perspectiveLayer.sublayerTransform on its CATransformLayer subLayers). Play with the projection matrix m34 field value to see how it affects the vanishing point.
Swift 4 updated answer:
updated version of Vojtec's answer.
func animate() {
var transform:CATransform3D = CATransform3DIdentity;
var topSleeve:CALayer
var middleSleeve:CALayer
var bottomSleeve:CALayer
var topShadow:CALayer
var middleShadow:CALayer
var mainView:UIView
let width:CGFloat = 300
let height:CGFloat = 150
var firstJointLayer:CALayer
var secondJointLayer:CALayer
var perspectiveLayer:CALayer
mainView = UIView(frame:CGRect(x: 50, y: 50, width: width, height: height*3))
mainView.backgroundColor = UIColor.yellow
view.addSubview(mainView)
perspectiveLayer = CALayer()
perspectiveLayer.frame = CGRect(x: 0, y: 0, width: width, height: height*2)
mainView.layer.addSublayer(perspectiveLayer)
firstJointLayer = CATransformLayer()
firstJointLayer.frame = mainView.bounds;
perspectiveLayer.addSublayer(firstJointLayer)
topSleeve = CALayer()
topSleeve.frame = CGRect(x: 0, y: 0, width: width, height: height)
topSleeve.anchorPoint = CGPoint(x: 0.5, y: 0)
topSleeve.backgroundColor = UIColor.red.cgColor
topSleeve.position = CGPoint(x: width/2, y: 0)
firstJointLayer.addSublayer(topSleeve)
topSleeve.masksToBounds = true
secondJointLayer = CATransformLayer()
secondJointLayer.frame = mainView.bounds;
secondJointLayer.frame = CGRect(x: 0, y: 0, width: width, height: height*2)
secondJointLayer.anchorPoint = CGPoint(x: 0.5, y: 0)
secondJointLayer.position = CGPoint(x: width/2, y: height)
firstJointLayer.addSublayer(secondJointLayer)
middleSleeve = CALayer()
middleSleeve.frame = CGRect(x: 0, y: 0, width: width, height: height);
middleSleeve.anchorPoint = CGPoint(x: 0.5, y: 0)
middleSleeve.backgroundColor = UIColor.blue.cgColor
middleSleeve.position = CGPoint(x: width/2, y: 0)
secondJointLayer.addSublayer(middleSleeve)
middleSleeve.masksToBounds = true
bottomSleeve = CALayer()
bottomSleeve.frame = CGRect(x: 0, y: height, width: width, height: height)
bottomSleeve.anchorPoint = CGPoint(x: 0.5, y: 0)
bottomSleeve.backgroundColor = UIColor.gray.cgColor
bottomSleeve.position = CGPoint(x: width/2, y: height)
secondJointLayer.addSublayer(bottomSleeve)
firstJointLayer.anchorPoint = CGPoint(x: 0.5, y: 0)
firstJointLayer.position = CGPoint(x: width/2, y: 0)
topShadow = CALayer()
topSleeve.addSublayer(topShadow)
topShadow.frame = topSleeve.bounds
topShadow.backgroundColor = UIColor.black.cgColor
topShadow.opacity = 0
middleShadow = CALayer()
middleSleeve.addSublayer(middleShadow)
middleShadow.frame = middleSleeve.bounds
middleShadow.backgroundColor = UIColor.black.cgColor
middleShadow.opacity = 0
transform.m34 = -1/700
perspectiveLayer.sublayerTransform = transform;
var animation = CABasicAnimation(keyPath: "transform.rotation.x")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = 0
animation.toValue = -90*Double.pi/180
firstJointLayer.add(animation, forKey: nil)
animation = CABasicAnimation(keyPath: "transform.rotation.x")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = 0
animation.toValue = 180*Double.pi/180
secondJointLayer.add(animation, forKey: nil)
animation = CABasicAnimation(keyPath: "transform.rotation.x")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = 0
animation.toValue = -90*Double.pi/180
bottomSleeve.add(animation, forKey: nil)
animation = CABasicAnimation(keyPath: "bounds.size.height")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = perspectiveLayer.bounds.size.height
animation.toValue = 0
perspectiveLayer.add(animation, forKey: nil)
animation = CABasicAnimation(keyPath: "position.y")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = perspectiveLayer.position.y
animation.toValue = 0
perspectiveLayer.add(animation, forKey: nil)
animation = CABasicAnimation(keyPath: "opacity")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = 0
animation.toValue = 0.5
topShadow.add(animation, forKey: nil)
animation = CABasicAnimation(keyPath: "opacity")
animation.duration = 2
animation.autoreverses = true
animation.repeatCount = 1000
animation.fromValue = 0
animation.toValue = 0.5
middleShadow.add(animation, forKey: nil)
}
}

How to animate the frame of an layer with CABasicAnimation?

I guess I have to convert the CGRect into an object to pass it to fromValue?
This is how I try it, but it doesn't work:
CABasicAnimation *frameAnimation = [CABasicAnimation animationWithKeyPath:#"frame"];
frameAnimation.duration = 2.5;
frameAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
frameAnimation.fromValue = [NSValue valueWithCGRect:myLayer.frame];
frameAnimation.toValue = [NSValue valueWithCGRect:theNewFrameRect];
[myLayer addAnimation:frameAnimation forKey:#"MLC"];
The frame property of a CALayer is a derived property, dependent on the position, anchorPoint, bounds and transform of the layer. Instead of animating the frame, you should instead animate the position or bounds, depending on what effect you are trying to accomplish.
To move a layer, you can animate the position:
-(void)moveLayer:(CALayer*)layer to:(CGPoint)point
{
// Prepare the animation from the current position to the new position
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [layer valueForKey:#"position"];
// NSValue/+valueWithPoint:(NSPoint)point is available on Mac OS X
// NSValue/+valueWithCGPoint:(CGPoint)point is available on iOS
// comment/uncomment the corresponding lines depending on which platform you're targeting
// Mac OS X
animation.toValue = [NSValue valueWithPoint:NSPointFromCGPoint(point)];
// iOS
//animation.toValue = [NSValue valueWithCGPoint:point];
// Update the layer's position so that the layer doesn't snap back when the animation completes.
layer.position = point;
// Add the animation, overriding the implicit animation.
[layer addAnimation:animation forKey:#"position"];
}
To resize a layer, you would animate the bounds parameter:
-(void)resizeLayer:(CALayer*)layer to:(CGSize)size
{
// Prepare the animation from the old size to the new size
CGRect oldBounds = layer.bounds;
CGRect newBounds = oldBounds;
newBounds.size = size;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"bounds"];
// NSValue/+valueWithRect:(NSRect)rect is available on Mac OS X
// NSValue/+valueWithCGRect:(CGRect)rect is available on iOS
// comment/uncomment the corresponding lines depending on which platform you're targeting
// Mac OS X
animation.fromValue = [NSValue valueWithRect:NSRectFromCGRect(oldBounds)];
animation.toValue = [NSValue valueWithRect:NSRectFromCGRect(newBounds)];
// iOS
//animation.fromValue = [NSValue valueWithCGRect:oldBounds];
//animation.toValue = [NSValue valueWithCGRect:newBounds];
// Update the layer's bounds so the layer doesn't snap back when the animation completes.
layer.bounds = newBounds;
// Add the animation, overriding the implicit animation.
[layer addAnimation:animation forKey:#"bounds"];
}
You can combine these animations using a CAAnimationGroup if you need to move and resize a layer at the same time.
we can change the properties of "bounds" and "position" to animate it, such as
-(void)handleTap2:(UITapGestureRecognizer *)recognizer {
UIImageView *vw = (UIImageView *)[recognizer view];
CGPoint startPoint = CGPointMake(vw.frame.size.width/2+vw.frame.origin.x, vw.frame.size.height/2+vw.frame.origin.y);
CGPoint endPoint = CGPointMake(160, 240);
CGRect startBounds = vw.bounds;
CGRect stopBounds = self.view.bounds;
layer = [CALayer layer];
layer.frame = self.view.frame;
layer.contents = (id)[vw.image CGImage];
[self.view.window.layer addSublayer:layer];
CABasicAnimation * baseAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
baseAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
baseAnimation.fromValue = [NSValue valueWithCGPoint:startPoint] ;
baseAnimation.toValue = [NSValue valueWithCGPoint:endPoint] ;
CABasicAnimation * boundsAnimation = [CABasicAnimation animationWithKeyPath:#"bounds"];
boundsAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
boundsAnimation.fromValue = [NSValue valueWithCGRect:startBounds] ; boundsAnimation.toValue = [NSValue valueWithCGRect:stopBounds] ;
CAAnimationGroup * group =[CAAnimationGroup animation];
group.removedOnCompletion=NO; group.fillMode=kCAFillModeForwards;
group.animations =[NSArray arrayWithObjects:baseAnimation, boundsAnimation, nil];
group.duration = 0.7;
[layer addAnimation:group forKey:#"frame"];
}
The question is antique, but I will answer it anyway.
Frame property is not animatable. You have to animate other properties. Also you have to disable implicit animations.
let updatedBounds = ...
let animation = CABasicAnimation(keyPath: "bounds")
animation.duration = 0.5
//it's better to start animation from presentation layer in case there is already animation going on
animation.fromValue = customLayer.presentation()?.bounds
animation.toValue = updatedBounds
customLayer.add(animation, forKey: nil)
//disable implicit animation for thoose properties
CATransaction.begin()
CATransaction.setDisableActions(true)
//update properties so they will be updated at the end of animation
customLayer.bounds = updatedBounds
customLayer.position = originalRect.origin
customLayer.anchorPoint = CGPoint(x: 0, y: 0)
CATransaction.commit()
Extension in swift 4
import UIKit
extension CALayer {
func moveTo(point: CGPoint, animated: Bool) {
if animated {
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = value(forKey: "position")
animation.toValue = NSValue(cgPoint: point)
animation.fillMode = .forwards
self.position = point
add(animation, forKey: "position")
} else {
self.position = point
}
}
func resize(to size: CGSize, animated: Bool) {
let oldBounds = bounds
var newBounds = oldBounds
newBounds.size = size
if animated {
let animation = CABasicAnimation(keyPath: "bounds")
animation.fromValue = NSValue(cgRect: oldBounds)
animation.toValue = NSValue(cgRect: newBounds)
animation.fillMode = .forwards
self.bounds = newBounds
add(animation, forKey: "bounds")
} else {
self.bounds = newBounds
}
}
func resizeAndMove(frame: CGRect, animated: Bool, duration: TimeInterval = 0) {
if animated {
let positionAnimation = CABasicAnimation(keyPath: "position")
positionAnimation.fromValue = value(forKey: "position")
positionAnimation.toValue = NSValue(cgPoint: CGPoint(x: frame.midX, y: frame.midY))
let oldBounds = bounds
var newBounds = oldBounds
newBounds.size = frame.size
let boundsAnimation = CABasicAnimation(keyPath: "bounds")
boundsAnimation.fromValue = NSValue(cgRect: oldBounds)
boundsAnimation.toValue = NSValue(cgRect: newBounds)
let groupAnimation = CAAnimationGroup()
groupAnimation.animations = [positionAnimation, boundsAnimation]
groupAnimation.fillMode = .forwards
groupAnimation.duration = duration
groupAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
self.frame = frame
add(groupAnimation, forKey: "frame")
} else {
self.frame = frame
}
}
}
Here's a simple, fully working, example which may help someone.
Just call .slideUp() on the class and it will slide up.
class Slidey: YourViewClass {
func slideUp() {
print("\n\n SLIDE")
let FF = layer.position
var TT = FF
TT.y -= 100
print(FF)
print(TT)
CATransaction.begin()
CATransaction.setDisableActions(true)
CATransaction.setCompletionBlock{ [weak self] in
print("DONE")
}
let a = CABasicAnimation(keyPath: "position")
a.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
a.isCumulative = false
a.autoreverses = false
a.isRemovedOnCompletion = true
a.repeatCount = 0
a.fromValue = FF
a.toValue = TT
a.duration = 0.70
layer.add(a, forKey: nil)
CATransaction.commit()
}
}
I guess you need to change your last line to make it work:
[myLayer addAnimation:frameAnimation forKey:#"frame"];
You may also set an action to the layer to make all frame changes animated with your animation:
CABasicAnimation *frameAnimation = [CABasicAnimation animation];
frameAnimation.duration = 2.5;
frameAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
myLayer.actions = [NSDictionary dictionaryWithObjectsAndKeys:frameAnimation, #"frame", nil];
In CALayer's actionForKey: method reference you can find how layer looks up for the actions to animated its properties.