iOS: -setFrame stretches the view after CGAffineTransformMakeRotation - ios

I have rotated a UIImageView by calling
myView.transform = CGAffineTransformMakeRotation(someRadians)
myView.layer.allowsEdgeAntialiasing = YES;
If I call...
[myView setFrame:CGRectMake(myView.frame.origin.x, myView.frame.origin.y-100, myView.frame.size.width, myView.frame.size.height)];
... my view gets stretched, rather then simply moving upward a bit.
Is this normal behaviour? Im I not supposed to set the frame after rotating a view? Is there a solution? Thanks.

From the UIView documentation:
Warning: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.
In other words, don't use a view's frame if it doesn't have the identity transform.

Turns out, as #Scott Berrevoets suggested, there is no way to use the frame property after making a transformation.
Instead we have to move the view with another transformation. In order to not make the move transformation override the rotation, we can concat the translation transformation to the view's existing rotation transformation.
CGAffineTransform moveTransform = CGAffineTransformMakeTranslation(0, -100);
myView.transform = CGAffineTransformConcat(myView.transform, moveTransform);

Related

CGAffineTransform does not effect autolayout

recently I have been working with CGAffineTransform and CATransform3D on views and layers. When I apply transformation at layer level and especially on view level, shouldn't autolayout updates the frames of related views. For example, consider following layout |H:[ViewA]-[ViewB]|. If I apply transformation on just ViewA.
//We can either
ViewA.transform = CGAffineTransformMakeScale(1.5, 1.5)
ViewA.layer.transform = CATransform3DMakeScale(1.5, 1.5, 1.0)
shouldn't ViewB auto adjust it rect since ViewA height, width or position has changed after transformation. I also tried calling
self.view.layoutIfNeeded()
Transformations don't affect the Frame or Bounds of a view or the frame will be in an undefined state. To illustrate that: image you have a rectangular view with a frame of X:10, Y:10, W:100, H:100 and you rotate that by 45 degrees...what should the frame be?
From Apple's docs about the transformation:
If this property is not the identity transform, the value of the frame
property is undefined and therefore should be ignored.
This explains why AutoLayout does not perform any updates.

Moving a Rotated UIButton

I have a problem. I'm working on making a game. As part of my game I need images to be rotated and then moved in the direction of the rotated angle inside a game loop (using an NSTimer). In essence I'm trying to create the effect of launching a projectile. The code works fine when moving in perpendicular directions such as 0, 90, 180, 270, and 360 degrees, but any other angle and the image starts to glitch out. The object on the screen maintains its correct bounds and contents, but the actual displayed image disappears. Does anybody know what the problem is or someway I could get around it? If needed, I can make and post a video of my problem so you can see what I'm talking about.
Here is a sample of the code I'm using. The "background" variable is just a UIImageView:
angle = 60;
background.transform = CGAffineTransformRotate(object.transform, angle*M_PI/180); //converts degrees to radians and rotates the image
background.frame = CGRectMake( background.frame.origin.x + cos(angle*m_PI/180)*32; background.frame.origin.y -sin(angle*M_PI/180)*32, background.frame.size.width, background.frame.size.height); //moves the image in the direction of the angle
For starters, there is a semicolon after the x origin in your CGRect instead of a comma. Was that just a typo?
The UIView documentation for frame states:
Warning: If the transform property is not the identity transform, the
value of this property is undefined and therefore should be ignored.
Changes to this property can be animated. However, if the transform
property contains a non-identity transform, the value of the frame
property is undefined and should not be modified. In that case, you
can reposition the view using the center property and adjust the size
using the bounds property instead.
So there you have it, you should not be trying to change the frame when setting a custom transform. You are only trying to adjust the position of the view anyway so just modify your code to adjust center instead of the origin coordinates.
To change the size, you can use the bounds.
CGRect bounds = myView.bounds;
bounds.size.width = whatever;
bounds.size.height = whatever;
myView.bounds = bounds;

Where to set CGAffineTransformMakeScale(2, 2);

I'm developing an iOS app with latest SDK.
I create a AVCaptureVideoPreviewLayer this way:
// Create the preview layer
_videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
[_videoPreviewLayer setFrame:self.view.bounds];
_videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.view.layer insertSublayer:_videoPreviewLayer atIndex:0];
And now I want to do this:
_videoPreviewLayer.affineTransform = CGAffineTransformMakeScale(2, 2);
But if I put it before [_videoPreviewLayer setFrame:self.view.bounds]; I get different results than I put it after.
Where do I have to apply scale?
And, if I want to set CGAffineTransformMakeRotation(-M_PI_2); // -90 degrees, where do I have to do it?
You should not use setFrame on a view whose transform is not the identity. According to Apple's documentation on the frame property of UIView:
If the transform property is not the identity transform, the value of this property [frame] is undefined and therefore should be ignored.
And in UIView.h, above the frame property
do not use frame if view is transformed since it will not correctly reflect the actual location of the view. use bounds + center instead.
So you will probably get the consistent results you're looking for if you switch to using setBounds and setCenter instead of setFrame in this code.
To answer the question directly: There's no correct place to put either of these transforms (the scale or the rotation) if you are also using setFrame. If you use setBounds and setCenter then you will get the same results whether you apply the transforms before setBounds/setCenter or after setBounds/setCenter.
EDIT: VansFannel points out that this is a CALayer, not a UIView, so the above comments don't really apply. I'm leaving them so as not to deprive VansFannel's comment of context, and also because it's still a good warning to have for UIViews
Now, for CALayers, if you set the frame (like [_videoPreviewLayer setFrame:self.view.bounds]) that will cause _videoPreviewLayer's actual rendered frame to be self.view.bounds even if _videoPreviewLayer has a non-identity transform. It's important to remember that CALayer's frame is actually a derived property. It's derived from the layer's bounds, position, anchorPoint, and transform. When you use setFrame QuartzCore will figure out a bounds and position that yields the frame you passed to setFrame under the existing anchorPoint and transform.
So if you want those transforms to have any effect you should put them after your call to setFrame. If you set the transforms first then setFrame will effectively negate them. If you absolutely need to set the transforms first then you must avoid setFrame and work with the layer's bounds and position directly.

images on iOS becoming narrow and squeezed when I apply rotation and transformation

I am using the following code to rotate and transform an image view:
myImageView.transform = CGAffineTransformMakeRotation(45); // rotation
CGRect frame = myImageView.frame;
frame.origin.x = x_position;
frame.origin.y = y_position;
myImageView.frame = frame; // transformation
tl;dr: The frame is bogus when you have a non-identity transform. Change the center instead.
From the documentation:
Warning If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.
When you are setting the transform on the first line and then reading the frame after it is undefined what you actually get back.
// Setting a non-identity transform (1)
myImageView.transform = CGAffineTransformMakeRotation(45);
CGRect frame = myImageView.frame; // At this point the frame is undefined (2)
// You are modifying something which is undefined from now on ...
Also, not only should you not read the frame because it is undefined, you should also not set it.
if the transform property contains a non-identity transform, the value of the frame property is undefined and should not be modified.
The solution to that problem comes in the next sentence of the documentation
In that case, you can reposition the view using the center property and adjust the size using the bounds property instead.
Since you are only changing the position I would suggest that you don't touch the frame and instead read the center of the image view and set it to it's new value. The center is not affected by the transform in the same way as the frame.
CGPoint center = myImageView.center;
center.x = x_center_position; // Note that the `center` is not the same as the frame origin.
center.y = y_center_position; // Note that the `center` is not the same as the frame origin.
myImageView.center = center;
Try to remove autoresizing Mask of ImageView
imageView.autoresizingMask = UIViewAutoresizingNone;
This may solve your problem

UIImageView gets blurry after layer transformation

I have a UIImageView add to a UIView as a subview. When I apply a transformation on the UIView's layer the UIImageView gets blurry. Why is that? How can this problem be resolved?
view.layer.position = newPosition;
I apply only this transformation.
Edit:
I've tested it and if I apply other transformations like these:
view.layer.transform = newTransform;
view.layer.zPosition = newZPosition;
then the blurry doesn't appear, only if I change the layer position.
Use
blurryDialog.frame = CGRectIntegral(blurryDialog.frame);
To set the frame coordinates to integer values
I found the answer. When you set the position of a layer or a view then the origin of it can be float values and this causes the blurry thing.
So the solution is to set also the frame's origin after you set the position. You just have to set the frame's position to integer values. On the UI this change won't be seen, but the image will not be blurry.

Resources