I have an animation that I'm adding to a view. In the animation, I have a cell that collapses as if it's being folded. I split the view into two separate layers and apply a rotation on each layer. This is working, however, the effect is more subtle than I'd like. I'm looking for a way to increase the gap between the ends of the centerline (top and bottom edges of each layer) extends in during the animation. Basically, the halves aren't tall enough to convincingly look like they're folding backwards. Here is what I currently have mid animation, the orange lines are approximately where I would want the lines to fall during the animation:
Here is the code handling the rotation:
- (CABasicAnimation *)foldLayerToValue:(CGFloat)toValue expanding:(BOOL)expanding
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
CATransform3D rotate = CATransform3DIdentity;
rotate.m34 = 1.0 / -2000.f;
rotate = CATransform3DRotate(rotateAndScale, [self radiansFromDegrees:toValue], 1.f, 0.f, 0.f);
animation.toValue = [NSValue valueWithCATransform3D:rotateAndScale];
return animation;
}
This gets added to a CATransaction that also moves the lower section down the correct amount during the animation. I tried adding a scale factor to the animation, but that has an affect on the entire layer, scaling both the top and bottom edges. I tried increasing the height of the layers as they rotate, but that also provided unsavory results.
Any ideas? Is there any way I can manually set the position on a corner by corner basis?
I think part of the problem is that my anchor points are on the center line. During rotation, the top and bottom edges are getting closer to the POV, rather than the center line getting farther away. You can see that the corners are off the screen slightly which is limiting the amount that you can see the effect. I had previously had a version with the anchor points on the top and bottom of the layers, but I was unable to get the two layers to remain "touching" as the animation occurred. I had tried adding a translate to the Z coordinate during the animation, but it seemed to have no effect on anything.
The value you are using (-1.0/2000.0) corresponds to an object being far away from the camera/eye and usually has a low perspective. If you want something that has more perspective then you should change it to something that corresponds to a smaller distance to the camera. Usually the range of circa 500-800 is a "normal" distance, so you are probably looking for something in the 200-400 range.
That is: change the .m34 value to -1.0/200.0 and there will be more perspective.
Related
I have a custom view, which I use for markup (draw lines and other figures on it). The view controller recognizes touches and gestures and passes info to the view so it can draw itself properly. Each figure has a label (CATextLayer) with some figure info on it (line length for example).
I added a rotation gesture recognizer to the view controller to rotate this drawing view. I want to rotate the view, but prevent labels from rotation (so they stay 0.0 degrees relative to the superview). For this I calculate the new drawing view's angle relative to the superview and set label's angle property to the negative value, so I can rotate them oppositely. For example, the view is rotated 30 degrees, then I rotate labels -30 degrees.
In the view drawing method which is responsible for drawing figures and setting the labels, I create new transform for each label each time the view needs to be redrawn:
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformRotate(transform, self.angle); //self.angle - rotation angle in radians
transform = CGAffineTransformScale(transform, scale, scale); //scale label (need this for pinch gesture, works as expected)
label.transform = transform;
All this stuff works if I rotate labels 90 degrees. If the degree is not 0, 90, 180, etc, the label's frame changes: shrinks or enlarges, with large angles it even disappears. I understand, that when you rotate the rectangle, it's frame should get bigger as it's pointed here: Why after rotating UIImageView size is getting changed?
Is there a way to prevent CATextLayer from changing its shape when rotating?
Normal position of label:
When rotated 90 degrees (everything is nice)
Rotated to some arbitrary angle (label frame is misshapen)
The issue was occurring because I was resetting CATextLayer's origin after setting its transform. According to the transform property documentation:
When the value of this property is anything other than the identity transform, the value in the frame property is undefined and should be ignored.
To change the position of a layer, use its position property.
The frame isn't that what you expected after a non-rectangular rotation.
I see three possible solutions for your problem:
Calculate the correct size of your text, and set the frame by assigning appropriate values to position and bounds.
Create a plain CALayer for the background of each text layer, if the first solution doesn't help.
But it should be much easier if you're rotate the image only and keep the rotation of the text layers unchanged. You can achieve this by adding the text layers to the super layer of the image layer. With this setup you have just to calculate the new position of the text layers after rotation.
I'm very unsure whether the first two solutions are practicable, and I would prefer the third one.
I'm using a variant of this code to slighty rotate a UIButton about its center:
CGFloat jiggleAngle = (-M_PI * 2.0) * (1.0 / 64.0);
self.transform = CGAffineTransformRotate(self.transform, jiggleAngle);
This code generally works as expected and rotates the button in-place, about 12 degrees counter-clockwise. However, this only works if I do not reposition the button inside my layoutSubviews method. If I do any repositioning of the button at all from its initially laid-out location, the attempt to rotate above results in the button's disappearance. If the angle I choose for rotation is an exact multiple of 90 degrees, the rotation works somehow even after a move in layoutSubviews.
I understand that my button's transform is being altered in layoutSubviews and this results in the subsequent weirdness when I attempt to rotate it with the above code. I have currently worked around this problem by placing the button I wish to rotate inside another UIView and then moving that view around as desired, but I'd like a better solution that doesn't require redoing my screen layouts.
How can I adjust/alter my button's transform after a move, so that this rotation code continues to work as expected?
The problem you are facing is that a view's frame is "undefined" if it's transform isn't the identity transform.
What you should do is use the view's center property to move it around. That works even if you've changed it's transform.
You can also apply a rotation to the view's layer instead of the view (although be aware that the layer transform is a CATransform3D, a 4x4 transformation matrix instead of a 3x3, so you need different methods to manipulate it.)
I tried to rotate an image by its bottom center by setting the anchorPoint property of the views layer to CGPointMake(0.5, 1).It did rotate based on the bottom center,but the image views bottom center was translated to the image views center position and thus the whole image was translated up by half the height of the image.How to have the image view retain its center but still rotate about its bottom center?
Has anyone ever encountered this issue before?
Hey guys heres a pictorial demonstration of how i want the rotation to work on the images!
This is the original untransformed image!
As you can see i have put a red dot to indicate the bottom center of the image.
Here is the rotated image.The image has been rotated clockwise by a few degrees.This has been rotated about by its center which is again not what i want!!
Image obtained after applying tranforms posted in Calebs answer..Note: The transforms were applied on an image view that houses the above vertical image!As you can see the flaws in the rotation the bottom center has gone up and even the rotation was different.It took sort of 180 degree rotation and the whole image translated up by some distance.
To reiterate,I just want the image to rotate like a needle of a clock but about its bottom center
Thank you!
Assuming that you want to rotate the view in which the image is drawn, the usual way to do it is to compose a transformation matrix from three separate matrices. The first one translates the image left by half its width. The second rotates the view. The third translates the image right by half its width (i.e. it's the inverse of the first). When you put these three together, you get a matrix that rotates the image about its bottom center.
For example, if you have:
CGFloat x = imageWidth/2.0;
CGFloat r = 1.0; // some value in radians;
you can set up a transform like this:
CGAffineTransform t1 = CGAffineTransformMakeTranslation(-x, 0);
CGAffineTransform t2 = CGAffineTransformRotate(t1, r);
CGAffineTransform t3 = CGAffineTransformTranslate(t2, x, 0);
t3 is the final transform that will translate, rotate, and translate back all in one step. If you set t3 as the transform for an image view, you'll find the view rotated by 1.0 radians (or whatever you set r to) about the bottom center.
In addition to correcting my earlier error, Peter Hosey's comment points out that another option is to rotate the layer instead of the view. I don't think this is what you're looking for, but if it is you should know that transformations on a layer take place about it's anchorPoint, which by default is set to the center of its bounds rectangle. To rotate about the bottom center of the bounds rect, you could set the anchor point to the bottom center first, and then apply a rotation transform. (Note that the transform property of a layer is a CATransform3D; if you want to use an affine transform as above, use the setAffineTransform: method.)
I have a UIView to which various scale and rotation CGAffineTransforms have been applied, in response to touch screen gestures.
When a scale operation completes, I want to adjust the bounds of the view to the new size, then have view to re-draw itself at a scale of 1.0, while keeping all other components of the transform the same.
To adjust the bounds of the view, I'm using:
self.myView.bounds = CGRectApplyAffineTransform(self.myView.bounds, self.myView.transform);
To "undo" the scale transform, I'm trying this:
self.myView.transform = CGAffineTransformConcat(self.myView.transform, CGAffineTransformMakeScale(1, 1));
then calling [myView setNeedsDisplay] to get the view to redraw itself.
However this does not produce the desired results and when a rotate transform is applied, the above code seems to cause what looks like a sideways translation transform to be applied too.
What's the cleanest way to "undo" just a scale transform and have the view redraw at 1:1 with all other transforms remaining intact?
I am handling something similar, and what I do is store the separate components in another struct (scale, rotation, translation). That way you can easily recreate the transform again (be careful about the order of your operations though).
One thing though: I suggest that you don't change the bounds and instead just apple a scale operation to the transform. This will avoid some potential unneeded layouts. All of this stuff can be handled purely with the transform property. The way you are doing it now is not going to change anything since applying a scale of 1 is a no-op. If you scaled up the view by 2, you would need to scale by 0.5 to get back to the original. Or, as I said above, store all the components and recreate it from the identity matrix (matrix math is fast, don't worry about that).
Save your scale values in a variable and then, try changing CGAffineTransformMakeScale(1, 1) into
CGAffineTransformMakeScale(1/totalScaleWidth, 1/totalScaleHeight)
I think my question can be summed up as how to store and reset the transform of a view. But then perhaps explaining my situation might help.
If I apply the transforms below to a view, one after another (like if I add this code to a switch or a button). I get exactly the result I would expect: the scale switches between: a view that is .55 times the size of the original view, and the view at it's original scale. Works to scale sub-views of someView too, just as I want. Ad infinitum. Perfect.
//tranformScale 1
someView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.55, 0.55 );
//tranformScale 2
someView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.0, 1.0 );
The trouble is I want to use this code (or similar) to scale a sub-view of self.view when an iOS device goes into landscape, to fit the sub-view to the landscape screen (and back up when in portrait). It almost works, but for some reason instead of outputting round values values for the frame of the view being scaled (as happens with a test using a button to call the transforms), progressively strange values are produced. Eventually, after about 4 rotations the sub-view flies off screen. I suspect it has to do with self.view changing shape, but then again, when I log the frame of self.view it's shape is very predictable.
By the way I am centering the view using autoresizingMask flexible margins, not using auto-layout. perhaps I should be centering the view with another type of calculation?
Thanks for reading!
I did this and it worked perfect.
self.imageview.transform = CGAffineTransformIdentity
(1) be sure not to set or get any frame data after applying transforms, it is unsupported and will yield unpredictable results;
(2) turn off your autoresizing mask flex margins and center like this:
view.center = CGPointMake(view.superview.bounds.size.width/2,
view.superview.bounds.size.height/2);
(take care to apply centering while view is at full size. i.e. BEFORE the transform if it is the resize transform, AFTER the transform if it is the identity transform)