I've been attempting to apply a perspective transform to achieve an effect similar to the Apple Maps 3D effect with the added constraint that the transform is applied once a certain zoom scale has been reached rather than clicking a button to enable the feature.
To achieve this I have been doing the following with very buggy results.
- (void)scrollViewDidEndZooming:(UIScrollView *)scr withView:(UIView *)view atScale:(CGFloat)scale {
NSLog(#"Scale: %f", scale);
CATransform3D transform = CATransform3DIdentity;
//z distance
float distance = [[UIScreen mainScreen] bounds].size.height;
transform.m34 = - 1.0/distance;
transform = CATransform3DRotate(transform, scale * 15 * M_PI / 180.0f, 1.f, 0.0f, 0.0f);
[UIView animateWithDuration:0.3 animations:^{
image.layer.transform = transform;
image.layer.zPosition = distance * ratio;
image.layer.position = CGPointMake([[UIScreen mainScreen] bounds].size.width/2, [[UIScreen mainScreen] bounds].size.height/3);
}];
previousScale = scale;
}
What ends up happening is that the perspective transform is applied the first time. However, it also translates the imageview, which is not what I want. Also, subsequent zoom attempts will seemingly continue rotating using the previous rotation as a base (I thought that setting the transform to the identity transform would reset this behavior).
Also, any pointers towards achieving the rotation effect would be much appreciated.
Simply, my question is as follows: How do I properly use the m34 transformation to achieve a transform effect similar to Apple Maps 3D effect.
Bonus Question: Can you point me in the right direction for achieving the rotation effect.
Thanks!
To achieve a sort of perspective changing the m34 value in the root view is not enough. The topic is quite complex and it actually requires a special hosting layer the CATransformLayer.
Here I've found a blog article that can help you. Rotation in layer language are applied in the anchor point.
Related
I am trying to perform an animation that does three things at once: translates, rotates and changes the size of an image.
I can do two at once, translate and size. However, when I add in rotation at the end of the following code, it is ignored. And if I place it at the beginning of the code, the size change is ignored. I've read that you can do a composite transition with view.transform, however, I have not been able to get that to work.
Here is my current code:
CGPoint destPoint = CGPointMake(-100,-50);
float radians =[self Degrees2Radians:-35];
[UIView animateWithDuration:2
animations:^{
//TRANSLATE
self.imageView.center = CGPointMake(self.imageView.center.x + destPoint.x, self.imageView.center.y + destPoint.y);
//ROTATE
self.imageView.transform = CGAffineTransformMakeRotation(radians);
//SCALE
self.imageView.transform = CGAffineTransformMakeScale(0.2, 0.2); // here the final size will be 20%
}
completion:nil
];
}
Can anyone recommend way to get all three things to occur simultaneously.
Here is some code for swift that uses the transform property of the view, but I have not been able to find the equivalent in Objective-C.
view.transform= CGAffineTransform(scaleX: 1.5, y: 1.5)
view.transform = view.transform.rotated(by angle: CGFloat(45 * M_PI / 180))
Thanks in advance for any suggestions.
You can use CGAffineTransformRotate function on the existing transform to apply a rotation. You can also use CGAffineTransformTranslate and CGAffineTransformScale to apply translation and scaling. Please note that, order of the operations matter.
For example if you have an existing transform myTransform you can rotate it like:
myTransform = CGAffineTransformRotate(myTransform, M_PI / 2);
The operation does not affect the input variable, instead, it returns a new transform so make sure you use the return value of the function. That's why I started the line with myTransform = ....
More information is available at https://developer.apple.com/documentation/coregraphics/cgaffinetransform-rb5?language=objc.
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;
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.
This might sound like a weird question, but what I am trying to do isn't very strange. I am currently resizing a UIView via the view's CGAffineTransform like this:
self.selectedController.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, resizeRatioValue, resizeRatioValue);
Where the resizeRatioValue is a value between 0.7-1.0, depending on the location of the gesture. This works great. With a pan gesture, I am able to shrink down my view beautifully.
Now, I would like to add another twist to this. As the view is shrinking, I would like to apply a rotation to the view (similar to the coverflow effect) so that it rotates and shrinks at the same time.
I can rotate the view just fine using this code:
float angle = 45.0 * progressRatio;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / 2000; // Perspective
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform,
1 * angle / (180.0 / M_PI), 0.0f, 1.0f, 0.0f);
self.selectedController.view.layer.transform = rotationAndPerspectiveTransform;
But when I put both of these together, it's one or the other. Whichever one occurs second is the one that works. I can't get them to coexist.
What can I do to make it so that these can both work together? Or is there a completely different approach that would be better suited for what I am trying to do?
Twitter for iPad implements a fancy "pinch to expand paper fold" effect. A short video clip here.
http://www.youtube.com/watch?v=B0TuPsNJ-XY
Can this be done with CATransform3D without OpenGL? A working example would be thankful.
Update: I was interested in the approach or implementation to this animation effect. That's why I offered bounty on this question - srikar
Here's a really simple example using a gesture recognizer and CATransform3D to get you started. Simply pinch to rotate the gray view.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// ...
CGRect rect = self.window.bounds;
view = [[UIView alloc] initWithFrame:CGRectMake(rect.size.width/4, rect.size.height/4,
rect.size.width/2, rect.size.height/2)];
view.backgroundColor = [UIColor lightGrayColor];
[self.window addSubview:view];
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1/500.0; // this allows perspective
self.window.layer.sublayerTransform = transform;
UIPinchGestureRecognizer *rec = [[UIPinchGestureRecognizer alloc] initWithTarget:self
action:#selector(pinch:)];
[self.window addGestureRecognizer:rec];
[rec release];
return YES;
}
- (void)pinch:(UIPinchGestureRecognizer *)rec
{
CATransform3D t = CATransform3DIdentity;
t = CATransform3DTranslate(t, 0, -self.view.bounds.size.height/2, 0);
t = CATransform3DRotate(t, rec.scale * M_PI, 1, 0, 0);
t = CATransform3DTranslate(t, 0, -self.view.bounds.size.height/2, 0);
self.view.layer.transform = t;
}
Essentially, this effect is comprised of several different steps:
Gesture recognizer to detect when a pinch-out is occurring.
When the gesture starts, Twitter is likely creating a graphics context for the top and bottom portion, essentially creating images from their layers.*
Attach the images as subviews on the top and bottom.
As the fingers flex in and out, use a CATransform3D to add perspective to the images.
Once the view has 'fully stretched out', make the real subviews visible and remove the graphics context-created images.
To collapse the views, do the inverse of the above.
*Because these views are relatively simple, they may not need to be rendered to a graphics context.
The effect is basically just a view rotating about the X axis: when you drag a tweet out of the list, there's a view that starts out parallel to the X-Z plane. As the user un-pinches, the view rotates around the X axis until it comes fully into the X-Y plane. The documentation says:
The CATransform3D data structure
defines a homogenous three-dimensional
transform (a 4 by 4 matrix of CGFloat
values) that is used to rotate, scale,
offset, skew, and apply perspective
transformations to a layer.
Furthermore, we know that CALayer's transform property is a CATransform3D structure, and that it's also animatable. Ergo, I think it's safe to say that the folding effect in question is do-able with Core Animation.