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.
Related
I have already tried looking around and cannot seem to find any info on this topic for exactly what I am looking for.
If you are familiar with Unity3D uGUI, I am trying to achieve the same "Filled" type Image effect there but inside of XCode using Objective-C.
If I have a faded image of a "character" and then as time goes on, I want to fill that "character" image with a "filled character" image in the same position. How can I work through this?
I have tried using MGImageResizingUtilities but this seems to change the size of the image being that the cropped image is no longer the same size of the original image being used and scale is set to Aspect Ratio.
It was hard to find a good example but here is something to get an understanding.
http://s22.postimg.org/9qghxosox/Screen_Shot_2016_01_30_at_17_08_06.png
Here is some code I am trying to use:
CALayer *maskLayer = [CALayer layer];
UIImage *maskImage = [UIImage imageNamed:#"turtle_posing"];
maskLayer.contents = (id)maskImage.CGImage;
maskLayer.bounds = self.filledTurtlePic.bounds;
CGRect turtleRect = self.filledTurtlePic.frame;
maskLayer.frame = CGRectMake(0, turtleRect.size.height, turtleRect.size.width, -turtleRect.size.height);
self.filledTurtlePic.layer.mask = maskLayer;
Here are two screenshots of an attempt to completely fill the image with the code above, and one screenshot with an attempt to partially fill the image (masklayer frame height is set to -40)
Thanks in advanced
The way I would do this is to create two images. Filled and unfilled.
Then create two image views. Fix the size so they are the same size. And in the same position. The unfilled one underneath and the filled one on top. When you run this it should look like only the filled one is on the screen.
Now create a CALayer and set it as the mask of the filledImageView.layer.
When you change the frame of this layer it will only show what is inside that frame. So it will hide part of the filledImageView and you will see the unfilledImageView underneath.
So, if your imageView has a frame of (100, 100, 100, 100) origin 100, 100, size 100, 100. The the mask layer frame should be (0, 0, 100, 100) for the full image. This is because the origin is relative to the image view.
For an empty image it should be (0, 100, 100, 0).
For x% the frame of the mask layer should be (0, x-100, 100, 100-x)
EDIT
You should have two image views...
filledImageView
unFilledImageView
These are setup correctly. Do not change the frames of these. Nor the layers of these.
You should create a layer and add it as a mask of the filledImageView.
self.maskLayer = [CALayer layer];
self.filledImageView.layer.mask = self.maskLayer;
Now have a function that updates the mask...
- (void)changeFilledImage:(CGFloat)percentage {
// percentage is from 0.0 to 1.0
CGFloat fullHeight = CGRectGetHeight(self.filledImageView.frame);
CGFloat width = CGRectGetWidth(self.filledImageView.frame);
CGFloat percentageHeight = fullHeight * percentage;
self.maskLayer.frame = CGRectMake(0, fullHeight - percentageHeight, width, percentageHeight);
}
Something like this should do.
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.
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.
I have a really annoying issue trying to draw into a bitmap CGContext. What I am trying to do is I have a couple of images to draw into the full size of the image. One can come in at any UIImageOrientation and I've written the code to correctly rotate that properly, but I'm struggling with the second bit which is trying to draw another view at an arbitrary rotation about its centre.
The other view comprises an image drawn possibly outside of its bounds. What I am having a problem with is drawing these at a rotated angle as though it was a UIView that had an affine transform applied to it. e.g. imagine a UIView at {100, 300} of size {20, 20} and an affine transform rotating it by 45 degrees. It would be rotated about {110, 310}.
What I have tried is this:
- (void)drawOtherViewInContext:(CGContextRef)context atRect:(CGRect)rect withRotation:(CGFloat)rotation contextSize:(CGSize)contextSize {
CGRect thisFrame = <SOLVED_FEATURE_FRAME_RELATIVE_TO_RECT_SIZE>;
thisFrame.origin.y = contextSize.height - thisFrame.origin.y - thisFrame.size.height;
CGRect rotatedRect = CGRectApplyAffineTransform(CGRectMake(0.0f, 0.0f, rect.size.width, rect.size.height), CGAffineTransformMakeRotation(-rotation));
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, rect.origin.x, contextSize.height - rect.origin.y - rect.size.height);
transform = CGAffineTransformTranslate(transform,
+(rotatedRect.size.width/2.0f),
+(rotatedRect.size.height/2.0f));
transform = CGAffineTransformRotate(transform, -rotation);
transform = CGAffineTransformTranslate(transform,
-(rect.size.width/2.0f),
-(rect.size.height/2.0f));
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, thisFrame, theCGImageToDraw);
CGContextConcatCTM(context, CGAffineTransformInvert(transform));
}
So what I am doing there, I think, is this:
Translate to the bottom left of rect which is where this view is meant to be drawn.
Translate by half the rotated size in x and y.
Rotate by the required angle.
Translate back half the original size in x and y.
I thought that this would be what I wanted to do because the first step translates the coordinate system to be such that thisFrame is drawn correctly relative to where we're being told to draw (by the rect method parameter). Then it's a pretty normal rotate about the centre of a rectangle.
The problem is that when rotated by say 45 degrees, the image is drawn slightly out of place. It's almost correct, but just not quite. When at 0, 90, 180 or 270 degrees then the position is pretty much spot on, maybe a few pixels out but when at 45, 135, 225, 315 degrees the position is too far up and to the right.
Can anyone see what I'm doing wrong here?
Update:
Silly me, it's bigger because I was passing in the wrong rect! Edited to get rid of references to it being the wrong size. It's still not quite in the right place though.
OK I have fixed it. The first point was that I was passing in the wrong rect at first as I was grabbing the frame from a UIView which had an affine transform applied to it, and as we all know the frame in that case is undefined. More likely it's the CGRect that comes from CGRectApplyAffineTransform(bounds, transform) but anyway, I fixed that one.
Then the main problem of drawing offset was fixed by changing my transform to this:
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, rect.origin.x, contextSize.height - rect.origin.y - rect.size.height);
transform = CGAffineTransformTranslate(transform,
+(rect.size.width/2.0f),
+(rect.size.height/2.0f));
transform = CGAffineTransformRotate(transform, -rotation);
transform = CGAffineTransformTranslate(transform,
-(rect.size.width/2.0f),
-(rect.size.height/2.0f));
That's what I had originally thought I should be doing, but for some reason I changed it to use the rotated CGRect.
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.