How can I move an rotated view relative to the coordinate system of it's superview? - iphone

When changing the center point of an 10 degrees rotated view, the specified coordinates for the center point will not match the coordinate system of the superview.
As an example, when you want to go straight up by y - 50, and no horizontal movement (i.e. x = theRotatedView.center.x), the shift in y-direction will be rotated. The view will go up and right, and not just up.
The rotation is done on the view, not on the layer.
I could wrap it in a superview that does the movement, while the subview does the rotation. Then I had no problems with converting. But maybe there is some simple solution. I just want to move the center of an rotated view as if the view was not rotated, i.e. when wanting y to be decremented by 10, just do so without worrying about the rotated coordinates. Are there any methods or functions for converting between the coordinate systems, so that I can just specify the center point coordinates in superview coordinates and it will just be right when applying these to the rotated view's center property?

Look at the CGAffineTransform methods, such as CGAffineTransformMake, for an easier way to generate the transforms you need. You can combine rotation and translation in one matrix.

You can multiply your coordinates by a transformation matrix. This puts your coordinates into your parent view's "coordinate space". You can also use the inverse matrix of the parent view to take coordinates that are in the parent view and return them to "world space".
http://en.wikipedia.org/wiki/Rotation_matrix
Read further down the page for pseudo-code examples.

Related

Rotating a rectangle using a UIPanGestureRecognizer

I have the following view where I'm using a pan gesture in the upwards or downwards direction to rotate it positively or negatively:
I'm wondering, is there a mathematical equation to precisely covert the amount panned to the amount it should be rotated so the timing is correct to keep the users finger on the view while it is rotating? For instance, if the pan translation comes back as 1, what would the proper amount be to rotate it?.
There are a few details you need to provide to give a meaningful answer:
Are you rotating the view about its centre (the default) or is there an anchor point?
Since the view is rotating, while the touch is moving strictly vertically in the superview, what's the expected behaviour as the view rotates further away from the vertical line defining the pan?
Is there a reason you're using a pan gesture instead of a rotation gesture, or even just direct touch tracking? It seems like it creates more problems than it solves.
I'm going to assume the view is rotating about its centre for the sake of simplicity, and I'll use a pan starting on the right side of the view as an example, with the rotation not exceeding ±90°. Here are two options:
Movement up and down translates linearly to the angle of rotation, i.e., a pan of a given distance rotates the view the same amount, no matter where the pan starts. In that case, you need to decide what the top and bottom limits of the pan are. They might be the bounds of the superview. Regardless, you want to convert the distance travelled in the Y direction to a value between -1 and 1, where -1 represents the bottom limit and 1 represents the top limit. Something like 2 * (dy / superview.bounds.size.height - 0.5). Multiply that by π/2 (M_PI_2 in math.h) to scale from the range [-1, 1] to the range [-π/2, π/2] and you've got the angle to add/subtract from the view's rotation at the beginning of the gesture.
The view tracks the touch so that its right edge is always "pointed at" the touch. In this case, pan isn't terribly useful because you only need the location of the touch in the superview, not the distance travelled. Calculate dx and dy as the difference in x and y coordinates from the view's centre to the touch location. Then calculate atan2(dy, dx) and you've got the absolute angle of rotation for the view.
I hope this puts you on the right track.
The answer is the angle would be panAmount.y / rectangleWidth.
Here is proof: https://math.stackexchange.com/questions/322694/angle-of-rotated-line-segment

Applying perspective that is always relative to the screen

I want to ask a question about the perspective that is achieved through CATransform3D.
I know that if you have a view that is 320x480 and then apply this:
CATransform3D perspective = CATransform3DIdentity;
CGFloat zDistance = 1000;
perspective.m34 = 1.0 / -zDistance;
view.layer.sublayerTransform = perspective;
you create a perspective that makes it look like the observer is looking straight at the center of the screen and therefore the same transformation looks different, depending on where the subview that is being transformed is located on the screen. For example, tilting a view looks like this when the view is in the middle of the screen:
And it looks like this if it's in the lower left corner:
Now, my problem is that making the perspective relative to the screen only works if the view I'm transforming is a subview of another view that is 320x480px big. But what if the view I want to transform is a subview of a view that is only 100x100px? Is there a way to make the perspective relative to the whole screen if the superview isn't the size of the screen?
Thanks in advance
According to apple
"The anchorPoint property is a CGPoint that specifies a location within the bounds of a layer that corresponds with the position coordinate. The anchor point specifies how the bounds are positioned relative to the position property, as well as serving as the point that transforms are applied around."
Your perspective should not be relative to the center of the screen or even to the center of your layer by default, is that where you have your anchor point? Aside from that though, what you seem to be asking is how to make your perspective appear to be relative to a different point. The trick is that your perspective is created by multiplying by your perspective matrix. Setting m34 to a small number does nothing magic, you are multiplying by your projection matrix.
YourProjection = {1,0,0 ,0,
0,1,0 ,0,
0,0,1 ,0,
0,0,-1.0/zdistance,1};
Remember that you can combine successive transforms by multiplying them together. Just transform your layer to wherever you want, then apply your Projection matrix, then transform it back, presto, perspective from a different origin.
float x = your coordinates relative to the screen center;
float y = same thing
TranslationMatrix = {1,0,0,0,
0,1,0,0,
0,0,1,0,
x,y,0,1};
ReverseTranslationMatrix = {1,0,0,0,
0,1,0,0,
0,0,1,0,
-x,-y,0,1};
//now just multiply them all together
Final = ReverseTranslation*YourProjection*Translation;
You will need to do the matrix math yourself, hopefully you already have a generic 4x4 column major matrix class that can do multiplication for you, if not i suggest you make one. Also, if you are interested, you might consider reading. This for an explanation of how the matrix you are currently using works, or This for a different take on projection matrices.

iPhone iOS which CGAffineTransform does isometric transform [ _ ] to /_/

I need to make a rectangular view [ ] appear as if it's top is rotated back, while the bottom is pinned in place: / \ . The resulting image is isometric with the bottom being wider than the top.
Which CGAffineTransform do I need to accomplish this goal?
As others have pointed out, you can't do this with a CGAffineTransform.
However, it's relatively easy to do with a CATransform3D, as I describe in this answer. You'll need to adjust the m34 component of the CATransform3D to give the transform some degree of perspective, rotate the view about the X axis, and potentially scale it so that the bottom edge remains at the same width as for your original unrotated view.
Alternatively, you might be able to adjust the anchorPoint of your view's underlying layer to be at the bottom, rather than the center. Rotations will then be applied from that edge, which should keep the bottom edge length constant and give you a receding perspective effect for the view. I believe a value of (0.5, 1.0) will set the anchorPoint to the lower edge.
Brad, I found this example (by you!) on how to do a perspective transformation:
http://www.sunsetlakesoftware.com/2008/10/22/3-d-rotation-without-trackball
For some reason it does not work in my code. My buttons have the 3d transform applied, but not the scaling effect.

How can I rotate an view based on an rotation origin, whoose position is specified in the view coordinate system?

I find this pretty confusing. When you want to rotate a view, it's going to be rotated by it's center point. But that's not always good. So if you need to rotate by an specified origin in your view, you would have to set the anchorPoint property of the view's layer.
Now, the problem is, that this anchorPoint property takes pretty confusing values for the average-intelligent programmer ;)
They're from 0 to 1, as far as I know. Also, the coordinate system is flipped to the view's coordinate system, where I think that 0 is the smallest value in the view's coordinate system, and 1 the largest.
myView.layer.anchorPoint = CGPointMake(0.5, 0.5);
would set the anchorPoint in the middle of the view.
What can I do, if I only know:
- my view is 100 x 150 units big
- I want the anchorPoint to be at x=100 and y=20, in my view coordinate system
Any idea how to achieve that?
Sounds like you would use
myView.layer.anchorPoint = CGPointMake(1.0, 0.133);
They are just percentage values, no? 100/100 = 1 and 150/20 = 0.133.

iphone cocoa : how to drag an image along a path

I am trying to figure out how can you drag an image while constraining its movement along a certain path.
I tried several tricks including animation along a path, but couldn't get the animation to play and pause and play backwards - so that seems out of the question.
Any ideas ? anyone ?
What you're basically trying to do is match finger movement to a 'translation' transition.
As the user touches down and starts to move their finger you want to use the current touch point value to create a translation transform which you apply to your UIImageView. Here's how you would do it:
On touch down, save the imageview's starting x,y position.
On move, calculate the delta from old point to new one. This is where you can clamp the values. So you can ignore, say, the y change and only use the x deltas. This means that the image will only move left to right. If you ignore the x and use y, then it only moves up and down.
Once you have the 'new' calculated/clamped x,y values, use it to create a new transform using CGAffineTransformMakeTranslation(x, y). Assign this transform to the UIImageView. The image moves to that place.
Once the finger lifts, figure out the delta from the original starting x,y, point and the lift-off point, then adjust the ImageView's bounds and reset the transform to CGAffineTransformIdentity. This doesn't move the object, but it sets it so subsequent accesses to the ImageView use the actual position and don't have to keep adjusting for transforms.
Moving along on a grid is easy too. Just round out the x,y values in step 2 so they're a multiple of the grid size (i.e. round out to every 10 pixel) before you pass it on to make the translation transform.
If you want to make it extra smooth, surround the code where you assign the transition with UIView animation blocks. Mess around with the easing and timing settings. The image should drag behind a bit but smoothly 'rubber-band' from one touch point to the next.
See this Sample Code : Move Me