I have a CALayer called photoLayer which has a sublayer called imageLayer whose contents is a CGImageRef. The user can select a crop rect in the image & also zoom with fingers using Pan/Zoom gestures (this selection is part of another UI using UIScrollView). To display the image in imageLayer with chosen crop rect and zoom value, I apply a scale transform to imageLayer to chosen zoom value. I also adjust imageLayer's position property to match the top left corner in the image crop rectangle selected using pan/zoom. This way I am able to display imageLayer with cropped/zoomed image correctly.
But now I also need to scale up photoLayer (i.e. parent layer of imageLayer) around the centre and when I apply the transform.scale animation to photoLayer, the imageLayer does not scale along the centre. I was expecting imageLayer to be unaffected by super layer's scale animation but that's not the case. What am I doing wrong ? Here is how I scale photoLayer :
CABasicAnimation *scale = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
[scale setFromValue:[NSNumber numberWithFloat:0.0f]];
[scale setToValue:[NSNumber numberWithFloat:1.0f]];
scale.beginTime = startTime;
scale.duration = 1.f;
[scale setRemovedOnCompletion:NO];
[scale setFillMode:kCAFillModeForwards];
[photoLayer addAnimation:scale forKey:#"scaleUp"];
I really need imageLayer to follow photoLayer's scale animation around centre.
Related
I want to rotate a CAShapeLayer with objective c around it center point without moving it around. The CAShapeLayer contain UIBezierPath point of rect. I'm not able to rotate the CAShapeLayer becouse i dont know how. Please show me how tp rotate around it center without moving it postion.
Here is some code that does that:
//Create a CABasicAnimation object to manage our rotation.
CABasicAnimation *rotation = [CABasicAnimation animationWithKeyPath:#"transform"];
totalAnimationTime = rotation_count;
rotation.duration = totalAnimationTime;
//Start the animation at the previous value of angle
rotation.fromValue = #(angle);
//Add change (which will be a change of +/- 2pi*rotation_count
angle += change;
//Set the ending value of the rotation to the new angle.
rotation.toValue = #(angle);
//Have the rotation use linear timing.
rotation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
/*
This is the magic bit. We add a CAValueFunction that tells the CAAnimation we are modifying
the transform's rotation around the Z axis.
Without this, we would supply a transform as the fromValue and toValue, and for rotations
> a half-turn, we could not control the rotation direction.
By using a value function, we can specify arbitrary rotation amounts and directions, and even
Rotations greater than 360 degrees.
*/
rotation.valueFunction = [CAValueFunction functionWithName: kCAValueFunctionRotateZ];
/*
Set the layer's transform to it's final state before submitting the animation, so it is in it's
final state once the animation completes.
*/
imageViewToAnimate.layer.transform = CATransform3DRotate(imageViewToAnimate.layer.transform, angle, 0, 0, 1.0);
//Now actually add the animation to the layer.
[imageViewToAnimate.layer addAnimation:rotation forKey:#"transform.rotation.z"];
(That code is taken (and simplified) from my github project KeyframeViewAnimations)
In my project I'm rotating the layer of a UIImageView, but the same approach will work for any CALayer type.
I'm trying to animate the rotation of a layer of a view over an arbitrary point. The start position of the animation will be a 90º rotation from the end and final position. This view occupies all the screen except the status bar.
I'm trying to use the following code (with a translation, followed by a rotation and an anti translation) but, however starting and ending in it, the animation isn't centered on the point, but wobbles around it.
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.duration = 1;
animation.additive = YES;
animation.removedOnCompletion = YES;
animation.fillMode = kCAFillModeRemoved;
//The point that should be used as the center of rotation
CGPoint cursorCenter=self.angleCenter;
//The center of the view
CGPoint linesCenter=lines.center;
//The transformation: translate->rotate->anti-translate
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DTranslate(transform, cursorCenter.x-linesCenter.x, cursorCenter.y-linesCenter.y, 0.0);
transform = CATransform3DRotate(transform, degreesToRadians(-90), 0.0, 0.0, -1.0);
transform = CATransform3DTranslate(transform, linesCenter.x-cursorCenter.x, linesCenter.y-cursorCenter.y, 0.0);
animation.fromValue =[NSValue valueWithCATransform3D:transform];
animation.toValue =[NSValue valueWithCATransform3D:CATransform3DIdentity];
[lines.layer addAnimation:animation forKey:#"90rotation"];
What I'm doing wrong?
You don't have to make translations to rotate over a aspecific point. Just change the anchorPoint of the layer to adjust center of the rotation.
The origin of the transform is the value of the center property, or
the layer’s anchorPoint property if it was changed.
About anchor point and how to specify it:
You specify the value for this property using the unit coordinate
space. The default value of this property is (0.5, 0.5), which
represents the center of the layer’s bounds rectangle. All geometric
manipulations to the view occur about the specified point. For
example, applying a rotation transform to a layer with the default
anchor point causes the layer to rotate around its center. Changing
the anchor point to a different location would cause the layer to
rotate around that new point.
Based on CALayer Class Reference and Core Animation Programming Guide
I have two separate images.
CurvedPath image (Attached Below)
PersonImage
As you can see this path is not having perfect arc for single angle.
I have another PersonImage that I want to animate exactly above the center line for this arc image with zoom-in effect.
This animation should be start from bottom-left point to top-right point.
How to achieve this kind of animation?
I have read about QuartzCore and BeizerPath animation, but as I am having less knowledge about those, it will quiet difficult to me to achieve this quickly.
Moving along a path
As long as you can get the exact path that you want to animate the image align you can do it using Core Animation and CAKeyframeAnimation.
Create a key frame animation for the position property and set it do animate along your path
CAKeyframeAnimation *moveAlongPath = [CAKeyframeAnimation animationWithKeyPath:#"position"];
[moveAlongPath setPath:myPath]; // As a CGPath
If you created your path as a UIBezierPath then you can easily get the CGPath by calling CGPath on the bezier path.
Next you configure the animation with duration etc.
[moveAlongPath setDuration:5.0]; // 5 seconds
// some other configurations here maybe...
Now you add the animation to your imageView's layer and it will animate along the path.
[[myPersonImageView layer] addAnimation:moveAlongPath forKey:#"movePersonAlongPath"];
If you've never used Core Animation before, you need to add QuartzCore.framework to your project and add #import <QuartzCore/QuartzCore.h> at the top of your implementation.
Creating a UIBezierPath
If you don't know what a bezier path is, look at the Wikipedia site. Once you know your control points you can create a simple bezier path like this (where all the points are normal CGPoints):
UIBezierPath *arcingPath = [UIBezierPath bezierPath];
[arcingPath moveToPoint:startPoint];
[arcingPath addCurveToPoint:endPoint
controlPoint1:controlPoint1
controlPoint2:controlPoint2];
CGPathRef animationPath = [arcingPath CGPath]; // The path you animate along
Zooming up
To achieve the zoom effect you can apply a similar animation using a CABasicAnimation for the transform of the layer. Simply animate from a 0-scaled transform (infinitely small) to a 1-scaled transform (normal size).
CABasicAnimation *zoom = [CABasicAnimation animationWithKeyPath:#"transform"];
[zoom setFromValue:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.0, 0.0, 1.0);];
[zoom setToValue:[NSValue valueWithCATransform3D:CATransform3DIdentity];
[zoom setDuration:5.0]; // 5 seconds
// some other configurations here maybe...
[[myPersonImageView layer] addAnimation:zoom forKey:#"zoomPersonToNormalSize"];
Both at the same time
To have both animations run at the same time you add them to an animation group and add that to the person image view instead. If you do this then you configure the animation group (with duration and such instead of the individual animations).
CAAnimationGroup *zoomAndMove = [CAAnimationGroup animation];
[zoomAndMove setDuration:5.0]; // 5 seconds
// some other configurations here maybe...
[zoomAndMove setAnimations:[NSArray arrayWithObjects:zoom, moveAlongPath, nil]];
[[myPersonImageView layer] addAnimation:zoomAndMove forKey:#"bothZoomAndMove"];
I am trying to make an effect where at first the entire screen is masked out. As a ball moves across the screen, the ball unmasks the area that it is in AND the areas that it WAS in remain unmasked.
I have the following code:
CALayer * ball = [CALayer layer];
ball.bounds = CGRectMake(0, 0, 42, 42);
ball.position = [[[alphabet controls] objectAtIndex:0] CGPointValue];
ball.contents = (id)([UIImage imageNamed:#"done.png"].CGImage);
[self.layer addSublayer:ball];
[self.layer setMask:ball];
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = path;
anim.repeatCount = HUGE_VALF;
anim.duration = 8.0;
[ball addAnimation:anim forKey:#"race"];
This animation masks the entire view and shows only what is behind the ball layer.
My question is : How can I keep unmasked the parts of the screen that were revealed?
Hmmm.
What you want is an image that contains all the pixels that the ball shape traveled through.
If you were animating the ball with frame-based animation, you could create a grayscale (or 1-bit) image and install that as the content of your mask layer. Then, as you moved the ball, you could draw it into your mask image with each frame.
I'm not sure how to get the same effect with Core animation.
You could make your mask a CAShapeLayer, create a CGPath that describes the entire path of your ball, and make that the path for your shape layer. If your ball is round, you could set the line thickness of the shape layer to your ball size. That would work. If you ball is an irregular shape, though, that approach wouldn't work. Quartz graphics on iOS doesn't have any way I know of to stroke a path with an arbitrary-shaped brush.
For certain reasons, I'm trying to avoid using a CAScrollLayer to do this. The effect I'm going after is to progressively reveal (from bottom to top) a CALayer's content (a png I previously loaded in). So I thought about doing this:
layer.anchorPoint = CGPointMake(.5, 1);
CABasicAnimation* a = [CABasicAnimation animationWithKeyPath:#"bounds.size.height"];
a.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
a.fillMode = kCAFillModeBoth;
a.removedOnCompletion = NO;
a.duration = 1;
a.fromValue = [NSNumber numberWithFloat:0.];
a.toValue = [NSNumber numberWithFloat:layer.bounds.size.height];
[layer addAnimation:a forKey:nil];
The problem with this is you can tell the layer's content is scaled with the bounds. I was trying for the bounds to change but the content to stay always the original size, so that effectively the bounds clip the image and as I increase bounds.height, the image "Reveals" itself.
Any ideas as to how to pull it off or what might I be missing?
Ok I got it to work, but I basically had to update the layer's frame too, to reflect the change in anchor point:
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
layer.contentsGravity = kCAGravityTop;
layer.masksToBounds = YES;
layer.anchorPoint = CGPointMake(.5, 1);
CGRect newFrame = layer.frame;
newFrame.origin.y += newFrame.size.height / 2;
layer.frame = newFrame;
[CATransaction setValue:(id)kCFBooleanFalse forKey:kCATransactionDisableActions];
a.toValue = [NSNumber numberWithFloat:layer.bounds.size.height];
[layer addAnimation:a forKey:nil];
"Dad" has the right answer.
You want to create a CAShapeLayer, and install that as the mask on your layer.
You create a CGPath that is just a rectangle and install that path into the shape layer. The contents of the path determine what areas of the masked layer show up. If the path is a triangle in the middle of the layer, then only the triangle appears.
You then create an animation that animates the path.
To reveal your image from the bottom, you'd set up a path that was a 0 height rectangle at the bottom of the layer, and then you'd create a CAAnimation where the toValue is the same rectangle with a hight of the full layer you want to reveal. The system would generate an animation that reveals the image in a sweep.
You can use this same technique to achieve all kinds of cool effects, like barn doors, venetian blinds, "iris wipes", etc.
What if you changed the clipping mask instead? (or use a mask layer).
You could put another image over the target image and move it up like a stage curtain.