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
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 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.
Changing the anchor point of a CALayer after a CATransform3dRotate gives weird results. I think the problem is, anchorPoint property is set on the previous state (no transform) of layer not the current state (after transform) of the layer.
Is there a way to change the anchorPoint of a CALayer's current state??
Code for Transform:
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -(1.0/800.0);
videoPlayerView.layer.zPosition = 100;
videoPlayerView.layer.transform = CATransform3DRotate(transform, (30*M_PI)/180, 1.0f, 0.0f, 0.0f);
Code to change the anchor Point
if(videoPlayerView.layer.anchorPoint.x != 0.0)
{
videoPlayerView.layer.anchorPoint = CGPointMake(0.0, 0.5);
videoPlayerView.layer.position = CGPointMake(videoPlayerView.layer.frame.origin.x - videoPlayerView.layer.frame.size.width/2,videoPlayerView.layer.position.y);
}
Basically, I have to rotate the Layer like a book flip. I have already done this without using the 30 degree transofrm, but I want it to look more 3D so applied the 30 degree transform along the x axis. So that it looks like the book is placed on a table.
And in order to rotate, setting the anchor point is necessary. If not, please advice otherwise...
instead of changing anchorPoint use
CATransform3DTranslate(transform, 50.0000f, 0.0000f, 0.0000f);
Translate along X by 50.00 points
Apply 0.40 percent of perspective or you can use the same code you are using right now
rotate the CAlayer using Y .
CATransform3D transform = CATransform3DIdentity;
CATransform3D tmp = CATransform3DIdentity;
transform = CATransform3DRotate(transform, 0.0146f, 0, 1, 0);
transform = CATransform3DTranslate(transform, 50.0000f, 0.0000f, 0.0000f);
tmp = CATransform3DIdentity;
tmp.m34 = 0.0040f;
transform = CATransform3DConcat(transform, tmp);
check the link for more reference.
let me know if it works.
I have been working at this for a while and searched SO thoroughly for a solution but to no avail. Here is what I am trying to do.
I have a UIScrollView on which the user can zoom and pan for 5 seconds. I have a separate CALayer which is not layered on top of the UIScrollView. I want to scale and translate this CALayer's contents to reflect the zoom and pans occurring on the UIScrollView. I want to achieve this via key frame animation CAKeyFrameAnimation. When I put this into code, the zoom occurs as expected but the position of the content is offset incorrectly.
Here is how I do it in code. Assume that UIScrollView delegate passes zoom scale and content offset to the following method:
// Remember all zoom params for late recreation via a CAKeyFrameAnimation
- (void) didZoomOrScroll:(float)zoomScale
contentOffset:(CGPoint)scrollViewContentOffset {
CATransform3D scale = CATransform3DMakeScale(zoomScale, zoomScale, 1);
CATransform3D translate = CATransform3DMakeTranslation(-scrollViewContentOffset.x,
-scrollViewContentOffset.y, 0);
CATransform3D concat = CATransform3DConcat(scale, translate);
// _zoomScrollTransforms and _zoomScrollTimes below are of type NSMutableArray
[_zoomScrollTransforms addObject:[NSValue valueWithCATransform3D:concat]];
// secondsElapsed below keeps track of time
[_zoomScrollTimes addObject:[NSNumber numberWithFloat:secondsElapsed]];
}
// Construct layer animation
- (void) constructLayerWithAnimation:(CALayer *)layer {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
animation.duration = 5.0;
animation.values = _zoomScrollTransforms;
// Adjust key frame times to contain fractional durations
NSMutableArray *keyTimes = [[NSMutableArray alloc] init];
for( int i = 0; i < [_zoomScrollTimes count]; i++ ) {
NSNumber *number = (NSNumber *)[_zoomScrollTimes objectAtIndex:i];
NSNumber *fractionalDuration = [NSNumber numberWithFloat:[number floatValue]/animation.duration];
[keyTimes addObject:fractionalDuration];
}
animation.keyTimes = keyTimes;
animation.removedOnCompletion = YES;
animation.beginTime = 0;
animation.calculationMode = kCAAnimationDiscrete;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:animation forKey:nil];
}
When the above layer is animated, the content is zoomed properly but it is positioned incorrectly. The content appears to be x and y shifted more than I expected and as a result doesn't exactly retrace the zoom/pans done by the user on the UIScrollView.
Any ideas what I may be doing wrong?
Answer
Ok, i figured out what I was doing wrong. It had to do with the anchor point for the CALayer. Transforms on CALayers are always applied to the anchor point. For CALayers, the anchor point is (0.5, 0.5) by default. So scaling and translations were being conducted along the center point. UIScrollViews, on the other hand, gives offsets from the top left corner of the view. Basically you can think of the anchor point for UIScrollView, for the purposes of thinking about the CGPoint offset value, as being (0.0, 0.0).
So the solution is to set the anchor point of CALayer to (0.0, 0.0). And then everything works as expected.
There are other resources that present this info in a nicer way. See this other question on Stackoverflow that is similar. Also see this article in Apple's documentation that discusses position, anchorpoint and general layer in geometry in great detail.
You are concatenating translation matrix with scaled matrix. The final value of displacement offset will be Offset(X, Y) = (scaleX * Tx, scaleY * Ty).
If you want to move the UIView with (Tx, Ty) offset, than concatenate the translate and scale matrices as below:
CATransform3D scale = CATransform3DMakeScale(zoomScale, zoomScale, 1);
CATransform3D translate = CATransform3DMakeTranslation(-scrollViewContentOffset.x,
-scrollViewContentOffset.y, 0);
CATransform3D concat = CATransform3DConcat(translate, scale);
Try this and let me know if it works.
I have a layer where I am modofying its m34 transform property to get perspective.
I would have expected that by changing the zPosition, the size will change (as it appears further away) however when I set the zPosition property, the size does not change, but it does when I use CATransform3DTranslate.
Why is this? What's the difference between the following:
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -4000;
myLayer.transform = transform;
myLayer.zPosition = -500;
and
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -4000;
transform = CATransform3DTranslate(transform, 0, 0, -500);
myLayer.transform = transform;
The latter works how I expect, but I want to understand why the first does not.
zPosition is just for the drawing order of siblings layers, not for perspective drawing: you can use it to get a "bring to front" / "send to back" effect without adding/removing the layer.