rotate UIImageView around an arbitrary point - ios

I have a UIImageView that I rotate around its center:
imageHorizon.layer.anchorPoint = CGPointMake(0.5, 0.5);
imageHorizon.transform = CGAffineTransformRotate(imageHorizon.transform, angleToRotate*(CGFloat)(M_PI/180));
Sometimes I also move this image to the left or right and then rotate again. I would like to keep the rotation center all the time on the same point (which is actually the center of the super view). How can I do that ?
cheers,

self.imgView.layer.anchorPoint = CGPointMake(0.0,1.0);
self.imgView.layer.position = CGPointMake(100,200.0);
CGAffineTransform cgaRotateHr = CGAffineTransformMakeRotation(-(3.141/4));
[self.imgView setTransform:cgaRotateHr];

This is an older question, but the other solutions did not work well for me, so I came up with another solution:
Rotating an image is essentially just a normal rotation with a translation applied, ensuring that the point you want to rotate around is still in the same spot after the rotation. To do this, calculate the position's CGPoint in your image before the rotation, get the position after the rotation, and apply the difference as a translation on the image, "snapping" it into the right position. Here is the code that I've been using:
Keep in mind that the translation should be applied via CGAffineTransform, not moving the .center, because the translation will need to be relative to the rotation, and CGAffineTransformTranslate() takes care of that.
// Note: self is the superview of _imageView
// Get the rotation point
CGPoint rotationPointInSelf = self.center; // or whatever point you want to rotate around
CGPoint rotationPointInImage = [_imageView convertPoint:rotationPointInSelf fromView:self];
// Rotate the image
_imageView.transform = CGAffineTransformRotate(_imageView.transform, angle);
// Get the new location of the rotation point
CGPoint newRotationPointInImage = [_imageView convertPoint:rotationPointInSelf fromView:self];
// Calculate the difference between the point's old position and its new one
CGPoint translation = CGPointMake(rotationPointInImage.x - newRotationPointInImage.x, rotationPointInImage.y - newRotationPointInImage.y);
// Move the image so the point is back in it's old location
_imageView.transform = CGAffineTransformTranslate(_imageView.transform, -translation.x, -translation.y);

You can make the image a subview of another view and then rotate the superview to get that effect. Another approach is to set the anchorPoint property as described in the docs.

I'm using this code to rotate around the point (0,0).
Maybe it help you figure out how to active what you want.
float width = self.view.frame.size.width;
float height = self.view.frame.size.height;
CGRect frame_smallView = CGRectMake(-width, -height, width, height);
UIView *smallView = [[UIView alloc] initWithFrame:frame_smallView];
smallView.backgroundColor = darkGrayColor;
// Select x and y between 0.0-1.0.
// The default is (0.5f,0.5f) that is the center of the layer
// (1.0f,1.0f) is the right bottom corner
smallView.layer.anchorPoint = CGPointMake(1.0f, 1.0f);
// Rotate around this point
smallView.layer.position = CGPointMake(0, 0);
[self.view insertSubview:smallView belowSubview:self.navBar];
[UIView animateWithDuration:1
animations:^{
smallView.transform = CGAffineTransformMakeRotation(M_PI);
}
completion:^(BOOL finished){
[self.navigationController popViewControllerAnimated:NO];
}];

Related

How do we rotate 2 UIView planes simultaneously in 3D space

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.

CGAffineTransformScale not scaling a gesture area

I have a 3x3 grid of UIViews with UIGestureRecognizers added to them, arranged like so:
The way it works is that tapping on a square enlarges it 2x using CGAffineTransformScale and overlays that over the other squares. The problem is that the touch area stays the same size as the 1.0 scale for some reason.
I add the squares using
CGRect squareFrame = CGRectMake(1 + squareSpacing + ((squareDimension + squareSpacing) * i), topMargin, squareDimension, squareDimension);
SquareView *square = [[SquareView alloc] initWithFrame:squareFrame];
[square setPosition:i];
square.layer.zPosition = 0;
[self.view addSubview:square];
[squaresArray addObject:square];
The Squares have gesture recognizers added in their init:
fingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
[self addGestureRecognizer:fingerTap];
The tapped function does the following:
[UIView animateWithDuration:1.0
delay:0.0
usingSpringWithDamping:0.8
initialSpringVelocity:10.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
CGAffineTransform transform = self.transform;
self.transform = CGAffineTransformScale(transform, 2.0, 2.0);
}
completion:nil];
I outlined the touch area in red. I have tried playing with the zPosition but I don't know what to do to make it work, I am stuck. I want to be able to tap the enlarged square anywhere for it to close, but I am limited to the red area.
Thanks in advance.
EDIT: Sorry that the pictures are so large. Code added.
Steven
You might trying bringing the expanded UIView to the front of your parent view so that your tap events aren't captured by any overlapped views:
[parentView bringSubviewToFront:tappedView];

Objective-C Transform to X and Y

I have been researching CGAffineTransforms and was wondering if there was a way to take your view and zoom in on an x,y coordinate. I have the scaling portion down with the function:
CGAffineTransformMakeScale(4.00 ,4.00);
However I am uncertain how to tie the scaling with a possible x,y coordinate. Has anyone ever done something like this? Am I incorrect in the use of these function possibly?
-(void)buttonSelected:(id)sender
{
UIButton *b = sender;
CGPoint location = b.frame.origin;
[UIView animateWithDuration:1.3f delay:0.0f options:UIViewAnimationCurveEaseIn animations:^{
CGAffineTransform totalTransform =
CGAffineTransformMakeTranslation(-location.x , -location.y );
totalTransform = CGAffineTransformScale(totalTransform, 4.0f, 4.0f);
totalTransform = CGAffineTransformTranslate(totalTransform, location.x , location.y );
[self.view setTransform:totalTransform];
}completion:^(BOOL finished) {
}];
}
You would either construct a transform that performed the three steps:
move point you want to scale around to the centre of the layer;
scale;
move the object back so that the original centre is back in the centre.
So, e.g.
// to use a point that is (109, 63) from the centre as the point to scale around
CGAffineTransform totalTransform =
CGAffineTransformMakeTranslation(-109.0f, -63.0f);
totalTransform = CGAffineTransformScale(totalTransform, 4.0f, 4.0f);
totalTransform = CGAffineTransformTranslate(totalTransform, 109.0f, 63.0f);
Or, arguably more simply adjust the view.layer's anchorPoint. The gotcha with the second idea is that when you first adjust the anchor point you'll get an immediate transform because all other positioning is in terms of the centre.

affine translation prevents CGPoint relocation

I expanded and moved a label (instructionLabel), then returned it to its original size but left it in the new position.
[UIView animateWithDuration:0.5
animations:^{
CGAffineTransform scale = CGAffineTransformMakeScale(2.0, 2.0);
CGAffineTransform translate = CGAffineTransformMakeTranslation(0.0, -70.0); // up 70
self.instructionsLabel.transform = CGAffineTransformConcat(scale,translate);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.25
animations:^{
CGAffineTransform scale = CGAffineTransformMakeScale(1.0,1.0);
CGAffineTransform translate = CGAffineTransformMakeTranslation(0,-70.0); //left in place up 70
self.instructionsLabel.transform = CGAffineTransformConcat(scale, translate);
}
completion:^(BOOL finished) {}
];
Later, I explicitly use CGPointMake to put the label back in its original spot, but it remains in the translated position (70 pts up from its original place).
instructionsLabel.frame = CGRectMake(384, 601, 655, 40);
//Adding this doesn't make any difference, in or out.
instructionsLabel.center=CGPointMake(384, 601);
I have verified by Breaks and NSLog that the CGPointMake and CGRectMake statements are reached...they just don't work after that affine transformation. Does anyone know why?
(I don't want to move the label back immediately after the translation routine, but I might have to if I can't figure out why the CGPointMake routine doesn't do it.)
Thanks for any suggestions.
-Rob
Unless Im mistaken one of the major reason for using affine transform to scale, move etc views is that, that you can later set transform to CGAffineTransformIdentity this will cancel out any transforms you have applied. I believe that your problem here is that you are setting center to what ever position it of before you applied translate transform. View indeed moves to that point + whatever transform is applied to that view. So just set transform identity.

How to track successive CGAffineTransforms so as to arrive at a specific point on screen?

How do you execute multiple CGAffineTransform operations (in animation blocks) without keeping track of every operation executed?
The translation operation doesn't take x,y coordinates but instead values to shift by. So unless you know where you are currently translated to, say at "location 2," how do you know what values to shift by to get to "location 3?"
For example:
A UIView's frame is at (0, 0) - Position 1. I set the transform to translate to (768, 0) and rotate -90 degrees - Position 2. Some time passes and now I want to move to (768, 1024) and rotate another -90 degrees - Position 3.
How do I know what to translate by to move from Position 2 to Position 3?
In context, I'm trying to achieve an iPad view like the following:
a UIView that takes up the entire screen
a UIToolbar that takes up the top edge and is on top of the UIView
when the iPad rotates, the UIView stays with the device, but the toolbar will rotate so that it is always on the top edge of the screen.
I am using CGAffineTransform translate and rotate to move the toolbar. Works great, except when I rotate the iPad multiple times. The first translate/rotate will work perfect. The following transforms will be off because I don't know the correct values to shift by.
UPDATE:
It looks like if I take the current translation (tx, ty) values in the UIView.transform struct and use the difference between them and the new location, it works. However, if the view has been rotated, this does not work. The tx and ty values can be flipped because of the previous rotation. I'm not sure how to handle that.
UPDATE 2:
Doing some research, I've found that I can get the original, unrotated points from tx, ty by getting the abs value of the points and possibly swapping x and y if the UIView is perpendicular. Now I am stuck figuring out how to correctly apply the next set of transforms in the right order. It seems no matter how I concat them, the UIView ends up in the wrong place. It just seems like this is all too complicated and there must be an easier way.
The answer is, apparently, you don't track the transforms.
So the way to rotate the toolbar around the screen is by not concatenating a rotate and translate transform. Instead, create a rotate transform and set the frame in the animation block. Further, based on the new UIInterfaceOrientation, set the degrees to rotate based on the compass values of 0, -90, -180, -270. Also, set the frame size base on the same locations.
So:
CGPoint portrait = CGPointMake(0, 0);
CGPoint landscapeLeft = CGPointMake(768 - 44, 0);
CGPoint landscapeRight = CGPointMake(0, 0);
CGPoint upsideDown = CGPointMake(0, 1024 - 44);
CGSize portraitSize = CGSizeMake(768, 44);
CGSize landscapeLeftSize = CGSizeMake(44, 1024);
CGSize landscapeRightSize = CGSizeMake(44, 1024);
CGSize upsideDownSize = CGSizeMake(768, 44);
CGFloat rotation;
CGRect newLocation;
switch(orientation) {
case UIDeviceOrientationPortrait:
NSLog(#"Changing to Portrait");
newLocation.origin = portrait;
newLocation.size = portraitSize;
rotation = 0.0;
break;
case UIDeviceOrientationLandscapeRight:
NSLog(#"Changing to Landscape Right");
newLocation.origin = landscapeRight;
newLocation.size = landscapeRightSize;
rotation = -90.0;
break;
case UIDeviceOrientationLandscapeLeft:
NSLog(#"Changing to Landscape Left");
newLocation.origin = landscapeLeft;
newLocation.size = landscapeLeftSize;
rotation = -270.0;
break;
case UIDeviceOrientationPortraitUpsideDown:
NSLog(#"Changing to Upside Down");
newLocation.origin = upsideDown;
newLocation.size = upsideDownSize;
rotation = -180.0;
break;
default:
NSLog(#"Unknown orientation: %d", orientation);
newLocation.origin = portrait;
newLocation.size = portraitSize;
rotation = 0.0;
break;
}
CGRect frame = newLocation;
CGAffineTransform transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(rotation));
if(lastOrientation) {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.3];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationBeginsFromCurrentState:YES];
}
toolbar.transform = transform;
toolbar.frame = frame;
// Commit the changes
if(lastOrientation) {
[UIView commitAnimations];
}
lastOrientation = orientation;
This works beautifully. However, an unexpected problem is that UI elements that iOS shows on your behalf are not oriented correctly. I.e., modal windows and popovers all keep the same orientation as the underlying UIView. That problem renders this whole thing moot.

Resources