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.
Related
So I have a UITextView that's supposed to be visually sitting on the bottom edge of the screen and then stretching up and back "into" the screen, "Star Wars" opening crawl-style.
After much googling etc, I feel like I have what looks like the right code for the job... but instead of setting up the perspective view I was looking for, this is just making the UITextView totally disappear!
The text view is set up in a storyboard with springs/struts (no autolayout) such that it's pinned at the top and bottom of the main view, about 20px in from each side, and the springs are active in both directions. Its outlet is hooked up to self.infoTextView. It shows up as I'd expect if I don't apply any transformations to it.
But when i fire off the code below in viewDidLoad, the text view just disappears completely. I'm sure I'm missing something but I can't seem to figure out what t is.
CGRect frame = self.infoTextView.layer.frame;
self.infoTextView.layer.anchorPoint = CGPointMake(.5f, 1.0f);
self.infoTextView.layer.frame = frame;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / 500;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 1.57, 0, 1, 0);
self.infoTextView.layer.transform = rotationAndPerspectiveTransform;
thanks!
There are two things you'll need to modify in your code:
The rotation axis. You're rotating it around the Y axis. To get the
effect you're after you will need to change the rotate transform to
turn around the X axis.
To get "Star Wars opening crawl-style" effect you'll need to set a negative angle or a negative perspective.
Also, you would probably want to set a more "strong" perspective to achieve a more dramatic effect.
Here's an example based on your transform code:
CGFloat angle = -45;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / 200;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, angle / 180.0 * M_PI, 1, 0, 0);
self.infoTextView.layer.transform = rotationAndPerspectiveTransform;
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'm writing an app such that sprites (subclasses of UIImageView) can be rotated, resized, and panned across the screen using gestures. I also would like to be able to apply a 3D perspective transformation to the sprites.
I have the rotate/resize/pan functionality working correctly, as well as the perspective transform. However, they don't seem to work together correctly. If I rotate an unmodified sprite, then try to skew it, the sprite 'resets' it's rotation, then applies the perspective. The opposite works though; if I skew first, I can apply any 2D transformation after without it resetting.
Here is the code I'm using: (rotate, resize, and pan are done using UIGestureRecognizers, whereas the skew uses a UISlider).
Rotate:
- (void)didRotateSprite:(UIRotationGestureRecognizer *)rotate
{
CGFloat angle = rotate.rotation;
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(spriteView.layer.transform, angle, 0, 0, 1);
spriteView.layer.transform = transform;
rotate.rotation = 0.0;
}
Resize:
- (void)didPinchSprite:(UIPinchGestureRecognizer *)pinch
{
CGFloat scale = pinch.scale;
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DScale(spriteView.layer.transform, scale, scale, 1);
view.layer.transform = transform;
pinch.scale = 1.0;
}
Perspective:
- (IBAction)perspectiveChanged:(UISlider *)slider
{
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -100;
transform = CATransform3DRotate(transform, (1 - (slider.value * 2)) * M_PI_2, 1, 0, 0);
spriteView.layer.transform = transform;
}
Thank you!
Found the answer with a lot of debugging and the help of this question. The trick was to transform the view's perspective using:
spriteView.superview.layer.sublayerTransform = transform;
This recursively applies the transformation to the view's superview and any subviews contained in it. For more information about this, check out the documentation and Apple's Layer Style Properties guide too.
I'm trying to create a "page flip effect" using UIView instead of CALayer due to a project limitation. This requires flipping 1 UIView 180 degrees and essentially "sticking it" to the back of the other UIView. You then rotate the two UIViews simultaneously by rotating the superview in 3D space.
I'm trying to port AFKPageFlipper's "initFlip" method to use UIView instead of UIImage.
Below is a snippet of my attempt to port it. The initial page flip works, but the "front layer" in the code doesn't seem to show up. As if I"m not able to see the backend of the page. When I'm flipping the page, the animation is initially correct (back layer is fine), but then the other side of the page (front layer), I see the inverted view of the first page (backLayer).
Any help would be awesome!
flipAnimationLayer = [[UIView alloc] init];
flipAnimationLayer.layer.anchorPoint = CGPointMake(1.0, 0.5);
flipAnimationLayer.layer.frame = rect;
[self addSubview:flipAnimationLayer];
UIView *backLayer;
UIView *frontLayer;
if (flipDirection == AFKPageFlipperDirectionRight)
{
backLayer = currentViewSnap2;
backLayer.layer.contentsGravity = kCAGravityLeft;
frontLayer = nextViewSnap2;
frontLayer.layer.contentsGravity = kCAGravityRight;
}else
{
backLayer = nextViewSnap2;
backLayer.layer.contentsGravity = kCAGravityLeft;
frontLayer= currentViewSnap2;
frontLayer.layer.contentsGravity = kCAGravityRight;
}
backLayer.frame = flipAnimationLayer.bounds;
backLayer.layer.doubleSided = NO;
backLayer.clipsToBounds = YES;
[flipAnimationLayer addSubview:backLayer];
frontLayer.frame = flipAnimationLayer.bounds;
frontLayer.layer.doubleSided = NO;
frontLayer.clipsToBounds = YES;
frontLayer.layer.transform = CATransform3DMakeRotation(M_PI, 0, 1.0, 0);
[flipAnimationLayer addSubview:frontLayer];
if (flipDirection == AFKPageFlipperDirectionRight)
{
CATransform3D transform = CATransform3DMakeRotation(0.0, 0.0, 1.0, 0.0);
transform.m34 = 1.0f / 2500.0f;
flipAnimationLayer.layer.transform = transform;
currentAngle = startFlipAngle = 0;
endFlipAngle = -M_PI;
} else
{
CATransform3D transform = CATransform3DMakeRotation(-M_PI / 1.1, 0.0, 1.0, 0.0);
transform.m34 = 1.0f / 2500.0f;
flipAnimationLayer.layer.transform = transform;
currentAngle = startFlipAngle = -M_PI;
endFlipAngle = 0;
}
Your code is rotating layers, not views. That's fine.
I would not expect the code you posted to animate, since a layer's backing view doesn't do implicit animation, You could make it animate by using a CABasicAnimation. Or, you could create layers for your front and back views and attach them as sublayers of your view's layers. If you do that than manipulating the transform on the layers will use implicit animations.
What I've done to create my own font-to-back flip as you describe is to fake it.
I animate in 2 steps: First from zero degrees (flat) to 90 degrees (where the layers become invisible.) At that moment I hide the first layer and make the second layer visible, rotated 90 degrees the other way, and then rotate the other layer back to zero. This creates the same visual effect as showing the back face of the rotation.
If you use implicit layer animation to do this you'll need to put the changes to the transform inside a CATransaction block and set the animation timing to linear, or use ease-in for the first half and ease-out for the second half. That's because animations default to ease-in,ease-out timing, and the first animation to 90 degrees will slow down at the end, and then the second 90 degree animation will ease in.
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.