Behaviour of CGAffineTransformMakeScale with CGREct and with UIView - objective-c

I have a view with a frame defined as (0,0,320,480).
I call transformation on this view:
self.myView.transform = CGAffineTransformMakeScale(factor, factor);
The view will scale preserving a central position on the screen and his frame after my changes will be for example (34,-8,251,376), as you can see X and Y are now different from 0.
If i use the same function on a CGRect with frame (0,0,320,480):
CGAffineTransform t = CGAffineTransformMakeScale(factor,factor);
CGRect rect2 = CGRectApplyAffineTransform(rect,t);
rect2 will preserve 0 for X and Y and i'll obtain as result something like (0,0,251,376)
Why X and Y for rect2 doesn't change as in UIView example ?

It's true that you're not technically supposed to look at the frame property of a UIView after transformation, but it's also not technically pertinent to the question you're asking.
When applying CAffineTransforms to a UIView, the transformation takes into consideration the UIView's backing CALayer's anchorPoint property. From the CALayer docs on anchorPoint:
Defaults to (0.5, 0.5), the center of
the bounds rectangle.
This means that when you apply that scale transform, it uses the center of the view as the anchor point, so the view scales around that location. I'm guessing if you were to set the anchor point to (0, 0), it would behave like CGRect does.
CGRect, on the other hand, is a simple C struct, and doesn't have a backing layer or an anchor point. Thus the difference in behavior.

The UIView reference page says specifically:
Warning: If the transform property is
not the identity transform, the value
of this property is undefined and
therefore should be ignored.
So don't look at a view's frame after setting it's transform.

Related

UIView's bounds size vs frame size

Can a frame's size be different from the bound's size of a UIView.
Whenever I set either of them, I notice that both change and they are always in sync. Is there an edge case where this is not true?
Yes; for example, a transformed (e.g. rotated) view has a different (and useless) frame size.
The frame is purely a convenience, and you could live entirely without it if you had to; the bounds size and center, together, accurately and always describe the view's position and size.
Yes, Please refer the below simple difference between frame and bound:-
The frame of a view is the rectangle, expressed as a location (x,y)
and size (width,height) relative to the superview it is contained
within.

The bounds of a view is the rectangle, expressed as a location (x,y)
and size (width,height) relative to its own coordinate system.
bounds "describes the view’s location and size in its own coordinate system".
frame "defines the origin and dimensions of the view in the coordinate system of its superview".
So the two should differ for any view that uses a different coordinate system than its parent. The key giveaway is:
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 that's an example Apple gives you of when frame is defined not to have a predictable relationship to bounds: whenever you've set a non-identity transform.
(source for all quotes was the UIView documentation)
They are different.
Assume I have a label:
label.frame = CGRect(x: 0, y: 0, width: 200, height: 20)
Its current frame & bounds (print(label.frame, label2.bounds)) are as follows:
(0.0, 0.0, 200.0, 20.0) (0.0, 0.0, 200.0, 20.0)
Note they are currently the same. It is shown in x-position, y-position, width, height (in that order).
Now I will apply a scale Y of 2 to the label like so:
label.transform = CGAffineTransform(scaleX: 1, y: 2)
Its new frame & bounds are as follows:
(0.0, -10.0, 200.0, 40.0) (0.0, 0.0, 200.0, 20.0)
Notice how its own bounds are still the same, while the frame has changed (height went from 20 to 40, and the y-position has shifted by 10 upwards to compensate for the 20 increase so it will remain centred).
This corresponds to what other answers/documentation are saying. Neither are useless, use it accordingly to your needs.
7 years late to the party but hope this still helps others.

Transform uibutton change height

After transform uibutton change height and setFrame does not work. After this. Help me. My code here:
NSLog(#"BEFORE_Frame_height = %f", nameBgBtn.frame.size.height);
NSLog(#"BEFORE_Bound_height = %f", nameBgBtn.bounds.size.height);
nameBgBtn.transform = CGAffineTransformMakeRotation(degreesToRadian(rndValue));
CGRect newFrame = CGRectMake(nameBgBtn.frame.origin.x,nameBgBtn.frame.origin.y, nameBgBtn.bounds.size.width, nameBgBtn.bounds.size.height);
[nameBgBtn setFrame: newFrame];
[nameBgBtn setBounds:newFrame];
NSLog(#"After_Frame_height = %f", nameBgBtn.frame.size.height);
NSLog(#"After_Bount_height = %f", nameBgBtn.bounds.size.height);
My logger:
2013-03-07 15:30:23.887 BEFORE_Frame_height = 46.000000
2013-03-07 15:30:23.888 BEFORE_Bound_height = 46.000000
2013-03-07 15:30:23.888 After_Frame_height = 49.887489
2013-03-07 15:30:23.888 After_Bound_height = 46.000000
There is difference between frame and bounds, especially when you are changing the transform. In your code you are mixing both and the result is not what you expect.
You apply some rotation by setting transform.
You create rectangle with origin of the new frame and size of bounds. The bounds didn't change using transform.
You set this rect to frame. The view does not move (the same origin), but it gets scaled down, because you are changing outer dimensions.
You set the same rect to bounds. I'm not sure what happens if you set bounds.origin to non-zero value, but the contents of button may be translated. Also it scales the button up, because bounds.size is set to the same as before.
To be clear:
bounds = rect in inner coordinate system, usually origin of zero (except for scroll views) and with desired size.
frame = rect in superview (outer) coordinate system, with any origin and the size may be the same as bounds.size. The frame is calculation of center, bounds and transform.
transform = how bounds are transformed to make frame. Mapping of inner to outer coordinates.
If you have button with size {50, 80} and you apply 90° rotation, the bounds.size will be the same {50, 80}, also center will not change, but frame reflects the new transformed size {80, 50}.
I hope it's clear now.
Update: Here is an image showing difference between frame and bounds.
Dark square is bounds, light square is frame. On the first image, they have the same size. On the second image, the view has rotated transform.

Why does my view move when I set its frame after changing its anchorPoint?

I made two instances of UILabel and added them to my ViewController's view.
And then I changed the anchorPoint of each from 0.5 to 1.0 (x and y).
Next, I reset the frame of uiLabel2 to its frame I created it with: (100,100,100,20).
When I run the app, uiLabel1 and uiLabel2 show at different positions. Why?
UILabel *uiLabel1 = [[[UILabel alloc] initWithFrame:CGRectMake(100, 100, 100, 20)] autorelease];
uiLabel1.text = #"UILabel1";
uiLabel1.layer.anchorPoint = CGPointMake(1, 1);
UILabel *uiLabel2 = [[[UILabel alloc] initWithFrame:CGRectMake(100, 100, 100, 20)] autorelease];
uiLabel2.text = #"UILabel2";
uiLabel2.layer.anchorPoint = CGPointMake(1, 1);
uiLabel2.frame = CGRectMake(100, 100, 100, 20);
[self.view addSubview:uiLabel1];
[self.view addSubview:uiLabel2];
A CALayer has four properties that determine where it appears in its superlayer:
position (which is the same as the view's center property)
bounds (actually only the size part of bounds)
anchorPoint
transform
You will notice that frame is not one of those properties. The frame property is actually derived from those properties. When you set the frame property, the layer actually changes its center and bounds.size based on the frame you provide and the layer's existing anchorPoint.
You create the first layer (by creating the first UILabel, which is a subclass of UIView, and every UIView has a layer), giving it a frame of 100,100,100,20. The layer has a default anchor point of 0.5,0.5. So it computes its bounds as 0,0,100,20 and its position as 150,110. It looks like this:
Then you change its anchor point to 1,1. Since you don't change the layer's position or bounds directly, and you don't change them indirectly by setting its frame, the layer moves so that its new anchor point is at its (unchanged) position in its superlayer:
If you ask for the layer's (or view's) frame now, you will get 50,90,100,20.
When you create the second layer (for the second UILabel), after changing its anchor point, you set its frame. So the layer computes a new position and bounds based on the frame you provide and its existing anchor point:
If you ask the layer (or view) for its frame now, you will get the frame you set, 100,100,100,20. But if you ask for its position (or the view's center), you will get 200,120.
Well that is exactly what an anchor point does. Before changing the anchor points, you were setting the frame based of the center of the label. After that, you are setting the frame based on the right bottom corner.
Since you were only resetting the frame for just one label, one adjusted its frame based on the new anchor point and the other one stayed at the old position.
If you want them to be at the same point, then you need to reset the frame for both of them after editing the anchor point, OR don't the anchor point at all.
This guide explains more about anchor points.

CALayer renderInContext: Position of Drawing

I have a UIView with a custom shape drawn in drawRect:. The frame property is set to:
{5.f, 6.f, 50.f, 50.f}
Now, I render the view in an Image Context, but it is ignoring the frame property of the UIView, and always drawing the UIView in the top left.
[_shapeView.layer renderInContext:UIGraphicsGetCurrentContext()];
I tried to change the frame of the CALayer, but nothing changed. Modifying the bounds property made things worse. The only work around I found useful was:
CGRect frame = _shapeView.frame;
CGContextTranslateCTM(context, frame.origin.x, frame.origin.y);
[_shapeView.layer renderInContext:context];
But, this is impractical when I am dealing with many shapes, I want to just use the frame property.
Using CGContextTranslateCTM is the proper way to go. As renderInContext: documentation states : "Renders in the coordinate space of the layer." This means the frame origin of your layer/view is ignored.
Regarding CGContextDrawLayer, it is not made to be used with CALayer, but with CGLayer. These are two very different things, and explains your crash.

What is the logic behind UIView's frame origin and size being read-only

I know that if you want to resize a UIView you have to do it like so:
CGRect frame = view.frame;
frame.size.height -= 100;
view.frame = frame;
instead of just:
view.frame.size.height -= 100;
My question is what could be the logic behind this? Is the setter for frame doing something extra, like maybe call setNeedsDisplay ?
Basically the reason behind it that the following line
view.frame.size.height -= 100;
actually does the following 2 things:
Calls [view frame] method that returns CGRect structure - copy of what is stored in your view
Then it changes the height field of the copy - so it does not affect the value of the structure stored in the view
Is the setter for frame doing something extra, like maybe call
setNeedsDisplay ?
Actually yes - view's frame property is calculated based on its origin, bounds and transform so setting the frame will also affect its origin and bounds (and depending on view's contentMode may force the view to redraw)