I'm attempting to rotate my calayer object in my callback for a rotation gesture recognizer as such:
CGFloat angle = [(NSNumber *)[hitlayer valueForKeyPath:#"transform.rotation.z"] floatValue];
hitlayer.transform = CATransform3DMakeRotation( (angle+90) / 180.0 * M_PI, 0.0, 0.0, 1.0);
[hitlayer setValue:[NSNumber numberWithFloat:(angle+90)] forKey:#"transform.rotation.z"];
This works fine the first time. But subsequent times, the value returned by transform.rotation.z is still the original value (not the transformed value) so the layer doesn't rotate any further. Any ideas what i'm missing ?
thanks
The transform property does not change the view's or layer's bounds or center. It just applies a transformation when it gets drawn, so it simply looks differently. So in your case, you have to keep track of the last degree value and add 90 on each call.
Related
I'm using CGAffineTransformMakeRotation to rotate a UIView inside an animation block, and I want to rotate it counterclockwise for 180º.
However when I put
myView.animateWithDuration(0.5)
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI))
it still rotates clockwise. What's confusing me is that if I write "manually" the value of M_PI, it works as expected
// this will rotate counterclockwise
myView.transform = CGAffineTransformMakeRotation(CGFloat(-3.14159265358979))
Why this behavior?
Edit: added I'm using an animation block
As I stated in my first comment and as Segii already wrote: the animation will take the shortest distance between where it's value is at now and where it is trying to go. The best solution imho is to use CABasicAnimation for forcing a counterclockwise rotation:
let anim = CABasicAnimation(keyPath: "transform.rotation")
anim.fromValue = M_PI
anim.toValue = 0
anim.additive = true
anim.duration = 2.0
myView.layer.addAnimation(anim, forKey: "rotate")
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI))
This will rotate it by 180 counter clockwise in one go.
The angle you specify in the last line + the fromValue is the starting angle -> 0. And then the rotation will go the distance (toValue - fromValue) = 0 - M_PI = - M_PI -> counter clockwise 180 degree rotation.
As I understood, you're trying to rotate view within animation block. animateWithDuration will always perform animation with the shortest way. So, M_PI and -M_PI gives the same destination position. The reason, why you get expected animation direction when setting it manually is that your manual value is really smaller that M_PI, so the shortest way to rotate is counterclockwise.
To get expected behaviour, you'll have to chain at least two animations,
splitting you rotation angle. For example
//first animation block
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI/2))CGAffineTransformMakeRotation;
//second animation block
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI/2))CGAffineTransformMakeRotation;
Swift 3.0
myView.transform = CGAffineTransform(rotationAngle: CGFloat(-M_PI/2))
Or, much more reliable, use CAKeyframeAnimation
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 using CGAffineTransformMakeRotation to rotate a UIView inside an animation block, and I want to rotate it counterclockwise for 180º.
However when I put
myView.animateWithDuration(0.5)
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI))
it still rotates clockwise. What's confusing me is that if I write "manually" the value of M_PI, it works as expected
// this will rotate counterclockwise
myView.transform = CGAffineTransformMakeRotation(CGFloat(-3.14159265358979))
Why this behavior?
Edit: added I'm using an animation block
As I stated in my first comment and as Segii already wrote: the animation will take the shortest distance between where it's value is at now and where it is trying to go. The best solution imho is to use CABasicAnimation for forcing a counterclockwise rotation:
let anim = CABasicAnimation(keyPath: "transform.rotation")
anim.fromValue = M_PI
anim.toValue = 0
anim.additive = true
anim.duration = 2.0
myView.layer.addAnimation(anim, forKey: "rotate")
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI))
This will rotate it by 180 counter clockwise in one go.
The angle you specify in the last line + the fromValue is the starting angle -> 0. And then the rotation will go the distance (toValue - fromValue) = 0 - M_PI = - M_PI -> counter clockwise 180 degree rotation.
As I understood, you're trying to rotate view within animation block. animateWithDuration will always perform animation with the shortest way. So, M_PI and -M_PI gives the same destination position. The reason, why you get expected animation direction when setting it manually is that your manual value is really smaller that M_PI, so the shortest way to rotate is counterclockwise.
To get expected behaviour, you'll have to chain at least two animations,
splitting you rotation angle. For example
//first animation block
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI/2))CGAffineTransformMakeRotation;
//second animation block
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI/2))CGAffineTransformMakeRotation;
Swift 3.0
myView.transform = CGAffineTransform(rotationAngle: CGFloat(-M_PI/2))
Or, much more reliable, use CAKeyframeAnimation
I've got a main CALayer which contains a few sublayers. I rotated the main layer by 89 degrees around the x-Axis, so I'm barely seeing it (it's close to being a slice in the middle of the screen now).
CATransform3D moveToOrigin = CATransform3DMakeTranslation(-self.bounds.size.width / 2.0f, -self.bounds.size.height / 2.0f, 0.0);
CATransform3D rotateX = CATransform3DConcat(moveToOrigin, CATransform3DMakeRotation(DEGREES_TO_RADIANS(89.0), 1.0, 0.0, 0.0));
CATransform3D finalTransform = CATransform3DConcat(rotateX, CATransform3DMakeTranslation(self.bounds.size.width / 2.0f, self.bounds.size.height / 2.0f, 0.0));
mainLayer.transform = finalTransform;
Now I want to move each sublayer along the z-Axis, so that it looks like a stack with spacing between the layers. Unfortunately, all I see is all sublayers flat on the main layer.
for (int i = 0; i < self.sublayers.count; i++) {
CALayer *aLayer = _layers[i];
transform = CATransform3DMakeTranslation(0.0, 0.0, 100 * i);
}
The weird thing is that moving along the x or y Axis works like a charm.
Does it have something to do with that sublayer transform?
Is changing the zPosition property the same as moving the layer with CATransform3D along the z-axis?
When layers are rendered they are generally being flattened so even though they may contain sublayers with different Z values, the rendered layer is going to be flat. However, there is one layer class which doesn't have this behavior and it is called CATransformLayer. It doesn't do have any contents of its own but it also leaves the sublayers intact.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Rotate CGPath without changing its position
I searched and tested a variety of code for a couple of hours and I can't get this to work.
I am adding an arbitrary UIBezierPath at a random location to a CAShapeLayer which gets added to a view. I need to rotate the path so that I can handle device rotations. I can rotate the layer instead of the path. I just need the result to be rotated.
I already have methods to handle transforming the bezier path by scaling and translation. It works great, but now I need to simply rotate 90 degrees left or right.
Any recommendations on how to do this?
Basic code:
UIBezierPath *path = <create arbitrary path>
CAShapeLayer *layer = [CAShapeLayer layer];
[self addPathToLayer:layer
fromPath:path];
// I could get the center of the box but where is the box center for the view it is in?
// CGRect box = CGPathGetPathBoundingBox(path.CGPath);
// layer.anchorPoint = ? How to find the center of the box for the anchor point?
// Rotating here appears to rotate around 0,0 of the view
layer.transform = CATransform3DMakeRotation(DegreesToRadians(-90), 0.0, 0.0, 1.0);
I see the following post:
BezierPath Rotation in a UIView
I suppose I could rotate as-is and then translate the path back into place. I just need to figure out what the translation values would be.
I should also state that what I am seeing after I try to rotate is that the image moves off-screen somewhere. I tried rotating 25 degrees to see movement and it pivots around the view's origin of 0,0 so that if I rotate 90 degrees the image is off-screen. I am running these test WITHOUT rotating the device - just to see how rotation works.
UPDATE #1 - 12/4/2012: For some bizarre reason if I set the position to a value I found empirically it moves the rotated bezier path into the correct position after rotation:
layer.position = CGPointMake(280, 60);
This values are a guess from starting/stopping the app and making adjustments. I have no idea why I need to adjust the position on rotation. The anchor point should be in the center of the layer. However, I did find that both the frame and position of a CAShapeLayer are all ZERO even though the path is set, and also the fact that the path is in the correct position within the view. The 280, 60 position shifts the path into what would be the center of the path bounding box when a rotation of +90 is made. If I change the rotation value I need to adjust the position. I should not have to do this manually adjustment.
I think a last resort is to somehow convert the bezier path to an image and then add it. I found that if I set the layer content to an image, then rotate, it rotates about its center point with no positional adjustment needed. Not so with setting the path.
UPDATE #2 12/4/2012 - I tried setting the frame and with fiddling I get it to center as follows:
CGRect box = CGPathGetPathBoundingBox(path.CGPath);
CGRect rect = CGRectMake(0, 0, box.origin.x + (3.5 * box.size.width), box.origin.y + (3.5 * box.size.height));
layer.frame = rect;
layer.transform = CATransform3DMakeRotation(DegreesToRadians(90), 0.0, 0.0, 1.0);
Why multiply by 3.5? I have no clue. I found that adding the box origin with about 3.5 times the size of the box shifts the rotated CAShapeLayer path to about where it should be.
There must be a better way to do this. This is a better solution than my previous post since the frame size does not depend on the rotation angle. I just don't know why the frame needs to be set to the value I am setting it to. I THOUGHT it should be
CGRectMake(0, 0, box.origin.x + (box.size.width / 2), box.origin.y + (box.size.height / 2));
However, it shifts the image to the left too much.
Another clue I found is that if I set the frame of [self view].frame (the frame of the entire parent view, which is the screen of the iPhone), then rotate, the rotation point is the center of the screen, an the path/image orbits around this center point. This is why I tried shifting the frame to what the center of the path should be so that it orbits around the box center.
UPDATE #3 12/4/2012 - I tried to render the layer as an image. However, it appears that just setting the path of a layer does not make it an "image" in the layer since it is empty
CGRect box = CGPathGetPathBoundingBox(path.CGPath);
layer.frame = box;
UIImage *image = [ImageHelper imageFromLayer:layer]; // ImageHelper library I created
CAShapeLayer *newLayer = [CAShapeLayer layer];
newLayer.frame = CGRectMake(box.origin.x, box.origin.y, image.size.width, image.size.height);
newLayer.contents = (id) image.CGImage;
It appears that rotating the layer with its path set is no different than simply rotating the bezier path itself. I will go back to rotating the bezier path and see if I can fiddle with the position elements or something. There's got to be a solution to this.
Goal: Rotate a UIBezierPath around its center point within the view it was originally created in.
UPDATE #4 12/4/2012 - I ran a series of tests measuring the values needed for translation in order to place a UIBezierPath in its previous center location.
CGAffineTransform rotate = CGAffineTransformMakeRotation(DegreesToRadians(-15));
[path applyTransform:rotate];
// CGAffineTransform translate = CGAffineTransformMakeTranslation(-110, 70); // -45
CGAffineTransform translate = CGAffineTransformMakeTranslation(-52, -58); // -15
[path applyTransform:translate];
However, the ratios of x/y translations do not correspond so I cannot extrapolate what translation is required based on the angle. It appears that 'CGAffineTransformMakeRotation' uses some arbitrary anchor put to make the rotation, which at the moment appears to be maybe (viewWidth / 2, 0). I am making this much harder than it needs to be. There's something I am missing to make a simple rotation so that the center point is maintained. I just need to "spin" the path 90 degrees left or right.
UPDATE #5 12/4/2012 - After running additional tests it appears that the anchor point for rotating a UIBezierPath is the origin from where all of the points were drawn. In this case the origin is 0,0 and all of the points are relative to that point. Therefore, it a rotation is applied, the rotation is occurring around the origin, and is why the path shifts up-right on -90 and up-left on 90. I need to somehow set the anchor point for the rotation to the center so it "spins" around the center, rather than the original origin point. 12 hours spent on this one issue.
After some detailed analysis and graphing the bounding box on paper I found my assertion that the origin of 0,0 is correct.
A solution to this problem is to translate the path (the underlying matrix) to the origin, with the center of the bounding box at origin, rotate, then translate the path back to its original location.
Here's how to rotate a UIBezierPath 90 degrees:
CGAffineTransform translate = CGAffineTransformMakeTranslation(-1 * (box.origin.x + (box.size.width / 2)), -1 * (box.origin.y + (box.size.height / 2)));
[path applyTransform:translate];
CGAffineTransform rotate = CGAffineTransformMakeRotation(DegreesToRadians(90));
[path applyTransform:rotate];
translate = CGAffineTransformMakeTranslation((box.origin.x + (box.size.width / 2)), (box.origin.y + (box.size.height / 2)));
[path applyTransform:translate];
Plug in -90 degrees to rotate in the other direction.
This formula can be used when rotating the device from portrait to landscape and vice/versa.
I still don't think this is the ideal solution but the result is what I need for now.
If anyone has a better solution for this please post.
UPDATE 12/7/2012 - I found what I think is the best solution, and very simple as I though it would be. Rather than using rotate, translate, and scale methods on the bezier path, I instead extract the array of points as CGPoint objects, and scale/translate them as needed based on the view size as well as the orientation. I then create a new bezier path and set the layer to this path.
The result is perfect scaling, translation, rotation.