Need a sort of a reverse CATransform3d - ios

I have two views (one is inside the another one).
The superview is "fullscreen" (it means it has frame == screenBounds and layer.transform = CATransform3DIdentity). So it looks like a rect.
The subview has the same frame but also has a defined transform which contains translation, rotation, scale and perspective. So it looks like a trapezium.
Additionally in my case the parent and the child frames don't intersect. So the parent view looks like a rect and the child one looks like a trapezium.
Which one complex transform should I apply to these both views to make the subview "fullscreen"? (after this transform the parent view will become a trapezium vice versa)
If it would be more clear for you I mean this transform:
http://www.youtube.com/watch?v=bjPQG43c_pE&t=1m00s
It occurs when you select one from safari pages 3D list.

Assuming your transform matrix of your inner view only contains affine transformations(i.e translate, rotate, scale, reflect, shear) all you need to do is invert your inner view's transformation matrix to transform it to the bounds of the outerview, assuming its frame is the same as the outerview's.
CATransform3D innerViewTransform = innerView.layer.transform;
outerView.layer.transform = CATransform3DInvert(innerViewTransform);
Note: transform is applied around the -anchorPoint property. FYI.

Related

CATransform3D - understanding the transform values

The picture shows a simple UIView after applying the following transform:
- (CATransform3D) transformForOpenedMenu
{
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0 /450;
transform = CATransform3DRotate(transform, D2R(40), 0, 1, 0);
transform = CATransform3DTranslate(transform, 210, 150, -500);
return transform;
}
I'm trying to make the distances highlighted with black to have equal length. Could you please help me understand the logic behind the values and calculations?
Cheers
UPD Sept 13
Looks like removing 3DTranslate keeps distances equal. I see I can use layer's frame property to reposition rotated view to the bottom left of the screen. Not yet sure, but this might actually work.
The .m34 value you are setting is best set on the sublayerTransform of the containing view rather than the view you are transforming.
I don't fully understand the maths behind affine transforms so I made this project which allows me to play around with the transform values to achieve the effect I want. You can plug in the values from your code above and see what it looks like, though note that there is already a perspective value applied using the sublayerTransform property mentioned above.
For your specific case, I think you want to adjust the anchor point of the layer to (0.0,0.5) and apply the rotation transform only. This assumes you want the menu to swing back like a door, with the hinges on the left edge.
The problem you're seeing is caused by your CATransform3DTranslate call. You're essentially setting the Y Axis off center, and hence seeing a different perspective view of the frame.
Think of it this way;
You're standing in the center of a long narrow field stretching off into the horizon. The edge of the field appears as if it is converges to a center point somewhere off in the distance. The angle of each edge to the converging point will appear equal if you are at the center of the field. If, on the other hand, you move either to the left or the right, the angles change and one will seem greater than the other (inversely opposite of course).
This is essentially what is happening with your view; As your converging points are to the right, changing the Y axis away from 0 will have the same effect as moving to the left or right in my example above. You're no longer looking at the parallel lines from the center.
so in your code above Setting the ty in CATransform3DTranslate to 0 Should fix your problem I.E.
transform = CATransform3DTranslate(transform, 210, 0, -500);
You may also need to alter the tz and tx Value to make it fit.
OK, so what eventually solved my question is this:
3D transform on Y axis to swing the view like a door transform = CATransform3DRotate(transform, D2R(40), 0, 1, 0);
set Z anchor point on a layer, to move it back targetView.layer.anchorPointZ = 850;
adjust layer position so that the view is located slightly to the bottom left of the parent view:
newPosition.x += 135 * positionDirection;
newPosition.y += 70 * positionDirection;
This sequence adjusts position without CATransform3DTranslate and keeps the 'swinged' effect not distorted.
Thanks everybody!

make subview descend from "behind" another subview

Within a UISrollView, I have several programmatically-added subviews representing nodes in a tree:
Each node's frame includes the node itself plus the line connecting it to its parent. (I did this to facilitate animation of the line with the node.) In the picture below, the frame is drawn for one of the nodes:
When the user taps on one of the nodes, two child nodes are "born". I'd like to animate this by having the child nodes descend down from behind the parent node. My basic animation code is:
- (void)descendFromParent
{
// Do nothing if this is root node
if (!self.parent)
return;
// Move node to parent location
self.frame = CGRectMake(self.frame.origin.x + self.parent.nodeFrame.origin.x - self.nodeFrame.origin.x,
self.frame.origin.y + self.parent.nodeFrame.origin.y - self.nodeFrame.origin.y,
self.frame.size.width, self.frame.size.height);
// Animate the move back to original location
[UIView animateWithDuration:1.0
delay:0.0
options:0
animations:^{
self.frame = self.trueFrame;
}
completion:nil];
}
(nodeFrame is a frame containing just the circular part of the view.)
The problem of course is that as the child node is descending, it (especially the line) is visible on top of and above the parent node:
I've tried a lot of different ways to make this work -- using CGContextClip, etc. -- but none of them work, mainly because drawRect: isn't called during the animation.
How can I make this work?
I think your problem is not how to arrange the views in the correct view hierarchy, but the following:
After you have arranged the child disks behind the parent disk, you want them to slide down, and while they are sliding, the edge that connects their centers should have first zero length (when all 3 disks are at the same place), and should then be extended until it reaches its final length in the end.
If this is the case, one possible solution would be:
Lets call the initial x,y center coordinate of one disk (0,0), and the final coordinate (X,Y). Using the usual animation code, it is easy to move the child center to (X,Y) in time t.
Now assume you have an additional image view that shows the edge along the diagonal. In the end position, the center of the "edge view" is at (X/2,Y/2), and its size is (X,Y). If this view is placed behind all others, it will connect the two disks at their final position.
The animation consists now of 1) changing the center property from the initial position (0,0) to (X/2,Y/2), and 2) changing the scale of the view (using its transform property) from zero to the final size, also in time t.
This should do it.
You may want to look at the UIView methods
-insertSubview:belowSubview:
-insertSubview:atIndex:
-sendSubviewToBack:
In your case, you can either send the two subviews to the background with -sendSubviewToBack: after you have added them. Or you can just add them with -insertSubview:atIndex: and provide 0 as the index.
You can specific an index for a subview, try inserting the nodes you want to animate at index 0 and have the blue nodes at a higher index.

Why does a transformed UIView overlap other views that are higher in the hierarchy?

I have a view to whose layer I applied transformations - altered the m34 field, rotated it on the x axis and scaled it on x and y. Then I added this view to a bigger superview. My issue is that every other view I add to the bigger superview gets hidden or overlapped by the transformed one (if the new view's frame intersects the transformed view's frame), even though the new views stand higher in the hierarchy than the transformed one and those new views get added to the end of the subviews array of the bigger superview. Any ideas what is the reason behind this behavior? :-) Thanks a lot!
3D transformations are used to make pseudo-3D. And in this 3D space your layer overlaps others. To change its z-pozition use CAlayer's property
#property CGFloat zPosition;
The layer’s position on the z axis. Animatable. The default value of
this property is 0. Changing the value of this property changes the
the front-to-back ordering of layers onscreen. This can affect the
visibility of layers whose frame rectangles overlap. The value of this
property is measured in points.
The view hierarchy is like a series of sheets of paper - they can be "on top" of each other, but there is no real depth. As soon as you put a 3D transform on a layer, this will have some depth and, if you've rotated it around the x axis, this will be sticking out in front of the other views.
You could try adjusting the z position of the layer to move it behind other views.

iOS - Core Animation - How to set origin when transform property is changed

I have a UIView added as a subview inside its parent view. When the user taps on this UIView, I want it to be transformed so it takes the entire screen.
I am trying to do so by changing its transform property and I was able to make its size match its parent view but not its position.
I read the Apple documentation and found frame cannot be used with transform. How do I make its position line up with its parent view?
Also, without using transform, is it a good idea to do so by changing its constraints, e.g., NSLayoutAttributeHeight?
i think you can simply change the frame after you scale it by transform like this:
//i am using 5 for testing purpose
self.backgroundImageView.transform = CGAffineTransformMakeScale(5, 5);
self.backgroundImageView.frame = CGRectMake(0, 0, self.backgroundImageView.frame.size.width, self.backgroundImageView.frame.size.height);
OR change the anchor point before the scale:
self.backgroundImageView.layer.anchorPoint = CGPointMake(0.5, 0.5);
self.backgroundImageView.transform = CGAffineTransformMakeScale(5, 5);
Update:
if you use Autolayout in your UIView, the solution might depends on how you set up your constraints, For example, if you set up your image constraints like this:
I would suggest still using the scale transform, but change the top and left constraint to 0.
or you set up your image constraint like this:
I would change the center alignment x and y to the center point of the super view
Maybe you can let me know how you set up you constraints, i can do some test for you on my local.

Cocoa Touch, set transform identity equal to current transform state

How do I change the transformIdentity. How do we set the transform "zero", or alter the transformIdentity of a view to the current state of the transform of said view.
In other words, I want to scale a view, and then set the current state (say scale of 2.5) to the default scale of the view (scale of 1).
example code:
view.transform = CGAffineTransformMakeScale(1, 2.5);
pseudo code for what I want to do:
view.transform = setTransformIdentityTo:view.currentState;
If I understand correctly transformIdentity is the state at which a scale would be 1, or a rotation would be zero, the default "zero" transform.
NOTE: The reason I want to do this is so that I can set a negative scale transform on only one axis of the view and alway get a flipped view relative to the last state of the view before the flip was invoked.
CGAffineTransformIdentity does reset a view or layer to its original, untransformed state, and thus cannot be redefined.
But if you want your "personal" reset transform, e.g. with a different scale, why don't you simply define it, e.g. by using CGAffineTransform myCGAffineTransformIdentity = CGAffineTransform CGAffineTransformMakeScale (sx,sy);, and apply it to your views?
selectedSticker.transform = CGAffineTransformIdentity;
Seems like this works for what I want to do:
CGAffineTransform trans = CGAffineTransformMakeScale(1, 2.5);
view.transform = CGAffineTransformConcat(selectedSticker.transform, trans);
What you can do, if you want to have your view still be transformed even when its own transform is the identity transform, is to put it inside another view that is transformed. Give the outer view the transform you want for your default; the inner view is the one where you do the actual work.
So, let's say you want your “identity” to be scaled by 2× horizontally. You set your outer view's transform to that transform, and leave it that way, and leave the inner view untransformed. When you want to add further transformation, you add it to the inner view, and when you want to reset it back to your default, you set the inner view's transform back to (true) identity. Your inner view will still scaled (or whatever) by the outer view's transform.
Note: I tried this briefly and found that (a) Auto Layout barfed on it, (b) scaling is outward from the anchor point, which means my outer view's horizontal scale pushed my inner view a little bit out-of-bounds, and (c) changing the outer view's anchor point to CGPointZero produced even further hilarity.
So, while this is theoretically a nice, simple, elegant solution, it may actually be more problematic than it's worth, in which case I recommend what Reinhard Männer suggested.

Resources