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.
Related
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 a UIView that I scale down when it is touched and scale back up when the touch is ended or cancelled.
I had been scaling the view like this
Scale down:
CGAffineTransform transform = CGAffineTransformMakeScale(0.95, 0.95);
self.transform = transform;
Scale up:
CGAffineTransform transform = CGAffineTransformMakeScale(1.0, 1.0);
self.transform = transform;
This doesn't preserve any other transform. I know I can use this to preserve the old transforms:
Scale down:
CGAffineTransform transform = CGAffineTransformScale(self.transform, 0.95, 0.95);
self.transform = transform;
Scale up:
CGAffineTransform transform = CGAffineTransformScale(self.transform, 1.0, 1.0);
self.transform = transform;
But of course here the scale up has no effect- plus there is potential to have cumulative scale down animations applied. Basically I want a way to apply the scale transform absolutely without affecting any other transform. Is there a way to do this? I don't think using 1.0/0.95 for the scale up factor, because it is possible the view could receive two touches before one is cancelled or ended.
I think I am asking the same thing as this question: Applying just the scale component of a CGAffineTransform to a UIView but I don't think the answers here will work for me.
I am targeting iOS 7 and above.
I am not sure if this is exactly what you are looking for but for me I need the scale to be absolutely consistent all the time so I modify the matrix directly.
CATransform3D transform = layer.transform;
if (makeSmaller)
{
// Scale to 0.9
transform.m11 = 0.9f;
transform.m22 = 0.9f;
}
// Cell needs to grow to normal size
else if (restoreToOriginal)
{
// Scale to 1.0 again
transform.m11 = 1.0f;
transform.m22 = 1.0f;
}
// Set the desired Y translation
transform.m42 = desiredffset;
layer.transform = transform;
I would like to transform a UIView in way that adds a perspective (with two vanishing points) to it (see below).
The reason why I want to do this with a view is because I want the contents of the transformed view to be the cells of a UITableView.
I am new to this kind of coding but I assume that I would have to change the CALayer that belongs to the subview. However, I believe, the transformation I am interested in cannot be created using CATransform3D.
Does anyone have an idea how to approach this problem?
It seems I have found a way to circumvent/solve the problem:
Using CATransform3D transformations it is possible to approximate the to vantage points by splitting the view into two parts (two independent UITableViews). These should be managed by a UIViewController (not the UITableViewController provided by x code) which implements the necessary and protocols.
Then in the viewDidLayoutSubviews: method use the following code to transform the two tableviews.
- (void) viewDidLayoutSubviews {
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
// left View
// vantage point
rotationAndPerspectiveTransform.m34 = 1.0 / -150.0;
// Z-rotation of 90°
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 90.0 * M_PI/180.0, 0, 0,1);
// X-rotation of 25°
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, -25.0 * M_PI/180.0, 1, 0,0);
// left view
[self.view viewWithTag:1].layer.transform = rotationAndPerspectiveTransform;
//right view
rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / -150;
// Z-rotation of 90°
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 90.0 * M_PI / 180.0, 0, 0,1);
// X-rotation of 30°
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 25.0 * M_PI / 180.0, 1, 0,0);
// right view
[self.view viewWithTag:2].layer.transform = rotationAndPerspectiveTransform;
}
Once transformed the two tableview can be shifted so that they neatly fit together. The only remaining job is to connect the scrolling of one tv with the other. I haven't figured that one out yet.
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.
I have an imageview that I am scaling and rotating via UISliders. Here is how I do so:
- (IBAction)sizeSlider:(UISlider *)sender
{
int SCALE_MAX = 200;
int SCALE_MIN = 10;
CGAffineTransform transform = image.transform;
float scale = sqrt(transform.a*transform.a + transform.c*transform.c);
if (scale > SCALE_MAX)
image.transform = CGAffineTransformScale(transform, SCALE_MAX/scale*sender.value, SCALE_MAX/scale*sender.value);
else if (scale < SCALE_MIN)
image.transform = CGAffineTransformScale(transform, SCALE_MIN/scale*sender.value, SCALE_MIN/scale*sender.value);
}
- (IBAction)angleSlider:(UISlider *)sender
{
CGAffineTransform t = CGAffineTransformMakeRotation(sender.value);
image.transform = t;
}
This works, however if the imageview has been scaled from its original size then rotating the image resets it to its original size. How can I prevent this?
Use the similar approach of your scale in your rotation. Meaning, use:
CGAffineTransformRotate(image.transform, theAngle);
CGAffineTransformMakeRotation
creates a new transform based on identity. Scaling is part of a transform--you want to use
CGAffineTransformRotate
instead which adds rotation to the existing, scaled transform.