When would a UIView's bounds.origin not be (0, 0)? - ios

When would an UIView's bounds.origin not be (0, 0)?
This paragraph was helpful to me:
IMPORTANT!! Bounds X and Y, the origin, are for moving inside the
View. For eample X+5, moving 5pix to the left of the frame's origin
meaning draw all content within this View to the left 5pix of frame's
origin. It doesn't do anything to itself, it is what being drew on it
that get affected.
But it describes only the case when I had set the value of bounds.origin myself.
In what other cases the value of bounds.origin != (0, 0)?

View's frame determines its location in superview. View's bounds determines its subviews locations. That means, if you change view's bounds, its location won't be changed, but all of its subviews location will be changed.
Positive width and height is like you draw a view from upper-left to bottom-right, while negative value is from bottom-right to upper-left. So
frame1 = CGRectMake(100, 100, -50, -50)
is totally identical with
frame2 = CGRectMake(50, 50, 50, 50).
And in fact, if you init a view with frame1, it will AUTOMATICALLY CHANGED to frame2.
But the bounds.origin of the views are not identical. Bounds.origin indicates the point that you "draw" the view, so all subviews frames will originate at this point.
For example, in Landscape iPhone 6, we have:
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 275, 275)];
leftView.backgroundColor = [UIColor greenColor];
[self.view addSubview:leftView];
UIView *rightView = [[UIView alloc] initWithFrame:CGRectMake(667-50, 375-50, -275, -275)];
rightView.backgroundColor = [UIColor blueColor];
[self.view addSubview:rightView];
And we got:
We will find that rightView's frame is automatically changed to positive value, which is (342, 50, 275, 275), but its bounts.origin = (-275,-275).
And we add subviews:
UIView *leftSubview = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
leftSubview.backgroundColor = [UIColor grayColor];
[leftView addSubview:leftSubview];
UIView *rightSubview= [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
rightSubview.backgroundColor = [UIColor grayColor];
[rightView addSubview:rightSubview];
So the bounds makes rightView's subview follows the origin which we init rightView.
If we change the bounds of rightView equals to leftView:
rightView.bounds = leftView.bounds;
Then the two subViews location is the same, we lost the information that rightView's width and height are negative.
And we change the bounds of leftView instead of rightView:
CGRect bounds = leftView.bounds;
bounds.origin = CGPointMake(50, 50);
leftView.bounds = bounds;
We can see, its subview's frame.origin is offset by bounds.origin(using minus, not plus).
To conclude:
view.bounds determines all its subview's location(offset by bounds.origin), while bounds will not affect its own location in its superview.
If you init a view with negative width and height, it will automatically changed to positive(which won't change the location), but its bounds.origin indicates the point that you start to "draw" the view.

A UIScrollView's bounds.origin will not be (0, 0) when its contentOffset is not (0, 0).

The bounds.origin will be negative if you initialize a view with negative width/height.
For example, if you did
UIView* v = [[UIView alloc] initWithFrame:CGRectMake(5, 5, -10, -20)];
the frame would be:
origin = {
x = -5,
y = -15
},
size = {
width = 10,
height = 20
}
bounds:
origin = {
x = -10,
y = -20
},
size = {
width = 10,
height = 20
}
center:
x = 0,
y = -5
try it for yourself!

(edited again because I can’t delete my original answer after it was accepted—credit for this goes to ian, who posted a more thorough answer below:)
In most cases this won’t happen. If you initialize your view with a negative width and/or height, you’ll get an origin with a negative X of the width and/or negative Y of the height.

Related

Convert scroll view frame to window coordinates

I am trying to convert scroll view coordinates to window coordinates. However the resulting frame seems to be shifted by status bar height, what's confusing is that the height remains the same which is not right.
CGRect visibleBounds = CGRectMake(0, 0, CGRectGetWidth(self.scrollView.frame), CGRectGetHeight(self.scrollView.frame));
CGRect scrollViewFrame = [self.scrollView convertRect:visibleBounds toView:nil];
lldb log:
Printing description of visibleBounds: (CGRect) visibleBounds =
(origin = (x = 0, y = 0), size = (width = 320, height = 568))
Printing description of scrollViewFrame: (CGRect) scrollViewFrame = (origin =
(x = 0, y = 20), size = (width = 320, height = 568))
Turns out scroll view bounds can be used to calculate the frame for scroll view in window coordinates, regardless the fact that I see negative bounds, produced frame will be correct anyway.
[self.scrollView convertRect:self.scrollView.bounds toView:nil];

Changing a views bounds affect the frame

I'm trying to understand how a view responds to its bounds being changed. If I change the bounds origin of a view, will it change the frame origin accordingly?
E.g.
UIView *greenView = [[UIView alloc] initWithFrame:CGRectMake(150, 150, 150, 200)];
greenView.backgroundColor = [UIColor colorWithRed:0.494 green:0.827
blue:0.129 alpha:1];
[self.view addSubview:greenView];
greenView.bounds = CGRectMake(0, 150, greenView.bounds.size.width, greenView.bounds.size.height);
Would this not change the frame's origin to (150, 300)? Running the code above doesn't seem to change its frame. (I know you're not meant to change a views position using bounds, this is just a hypothetical).
Per Apple Documentation, here are the relationship between a view's frame, bounds and centre:
Although you can change the frame, bounds, and center properties
independent of the others, changes to one property affect the others
in the following ways:
When you set the frame property, the size value in the bounds property changes to match the new size of the frame rectangle. The
value in the center property similarly changes to match the new
center point of the frame rectangle.
When you set the center property, the origin value in the frame changes accordingly.
When you set the size of the bounds property, the size value in the frame property changes to match the new size of the bounds rectangle.
So, answer to your question, changing X,Y position on View's bounds should not affect frame. Most of the cases bounds starts with (0,0). Changing the height or width to negative values would allow origin of bounds to go negative.
EDIT: To answer OP question - No, changing the position of bounds won't affect frame in any way. Since bounds is with reference to view's own co-ordinate system, changing X,Y on self co-ordinate system would not change position in superview's co-ordinate system.
You can try by using two custom views like this:
UIView* view1 = [[UIView alloc] initWithFrame:CGRectMake(50.0f, 100.0f, 150.0f, 150.0f)];
view1.backgroundColor = [UIColor redColor];
NSLog(#"view1.bounds = %#", NSStringFromCGRect(view1.bounds));
NSLog(#"view1.frame = %#", NSStringFromCGRect(view1.frame));
UIView* view2 = [[UIView alloc] initWithFrame:CGRectInset(view1.bounds, 20.0f, 20.0f)];
view2.backgroundColor = [UIColor yellowColor];
NSLog(#"view2.bounds = %#", NSStringFromCGRect(view2.bounds));
NSLog(#"view2.frame = %#", NSStringFromCGRect(view2.frame));
NSLog(#"view1.bounds = %#", NSStringFromCGRect(view1.bounds));
NSLog(#"view1.frame = %#", NSStringFromCGRect(view1.frame));
NSLog(#"view2.bounds = %#", NSStringFromCGRect(view2.bounds));
NSLog(#"view2.frame = %#", NSStringFromCGRect(view2.frame));
[view1 addSubview:view2];
And then change the subview bound like this:
CGRect frame = view2.bounds;
frame.origin.x += 20.0f;
frame.origin.y += 20.0f;
view2.bounds = frame;
Changing the bounds would not impact frame at all. And both the views would look same on screen:
And finally, try by changing the bounds of parent view to see below screen:
CGRect frame = view1.bounds;
frame.origin.x += 20.0f;
frame.origin.y += 20.0f;
view1.bounds = frame;

Set a UIView in a fixed proportional position from the Right Edge of screen iOS

Say, I have a UIImageView or any object and I set it in a UIView as subView with CGRectMake like this:
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 30)];
UIImageView *imgView = [[UIImageView alloc]initWithFrame:CGRectMake(266, 0, 30, 30)];
[imgView setImage:[UIImage imageNamed:[self.imageDicKey objectAtIndex:section]]];
[headerView addSubview:imgView];
Here I set the position of my imgView like CGRectMake(266, 0, 30, 30)]; and it counts it's position form the left x position (which is 266). What If I want to set my imgView's position from right side of the screen? So that,in different width of iPhone screen it shows in a same ratio position. But which will be counted it's position from right edge.
Thanks a lot in advanced.
I would recommend not hard coding the exact positions and instead calculating the x and y. That way it will help for different sized screens so it's not an exact position and rather it's based upon the views' size.
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 30)];
CGFloat padding = 20; // amount for padding
CGSize size = CGSizeMake(30, 30); // size of imageView
CGPoint startPos = CGPointMake(headerView.frame.size.width - padding - size.width, 0); // starting position for imageView
UIImageView *imgView = [[UIImageView alloc]initWithFrame:CGRectMake(startPos.x, startPos.y, size.width, size.height)]; [imgView setImage:[UIImage imageNamed:[self.imageDicKey objectAtIndex:section]]]; [headerView addSubview:imgView];
Well, since the ImageView's width is 30 and the x coordinate is 266 from the left side, it makes the x coordinate from the right side 296.

Reduce a UIView's Height From the Top Down?

Resizing a UIView's height can be done by modifying it's frame.size.height property OR by modifying it's NSLayoutHeight constraint. However, by default, reducing a UIView's height will cut off the UIView from the bottom up, I need to reduce a UIView's height from the top down. Is this possible?
Adjust the origin.y of the view as you change the height.
CGFloat const delta = 100;
[aView setFrame:CGRectMake(CGRectGetMinX([aView frame]), CGRectGetMinY([aView frame]) + delta, CGRectGetWidth([aView bounds]), CGRectGetHeight([aView bounds]) - delta)];
You will need to adjust the origin of the frame as well.
Eg:
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 20.0, 40.0)];
to change the height to 30.0 from the 'top down' do:
view.frame = CGRectMake(0.0, 10.0, 20.0, 30.0);
Create container UIView with clipsToBounds == YES, and move your view inside the container view.
Just like clipping mask in drawing software.

Adding subviews programmatically positions them weirdly

I have a view in which I want to add multiple subviews, however when I add them, they get positioned in positions where I didn't set the frame. The x coordinate is correct, but the y is quite off.
Using the Interface Builder it went quite smooth, just drag them in and send the correct frame and origin. However I don't appear to be able to set the origin; I get expression is not assignable when I try view.frame.origin = CGPointMake(x, y), and setting the x and y coordinates directly gives me the same error.
Does this happens because subviews cannot overlap programmatically without setting a special attribute (that I'm missing)?
Edit: The views are being set in the initWithStyle method of a UITableViewCell.
Edit 2: Added code within initWithStyle method.
// Initialize images
self.imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"image"]];
self.anImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"anImage"]];
self.anotherImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"anotherImage"]];
// Set imageview locations
self.imageView.frame = CGRectMake(0, 0, 300, 54);
self.anImageView.frame = CGRectMake(20, 53, 16, 52);
self.anotherImageView.frame = CGRectMake(179, 43, 111, 53);
To avoid expression is not assignable, you have to set the entire frame at once, either using
view.frame = CGRectMake(x, y, width, height)
or
CGRect frame = self.view.frame;
frame.origin.x = newX;
self.view.frame = frame;
Most likely you are setting the frames in the viewDidLoad method. The problem here is that you are setting the frame before the viewControllers frame has been resized based on the constraints of the app.
Try moving your frame setting into the method viewWillAppear and see if that fixes your problem.
Edit: Because you are doing this in a cell, and not in a viewController you would do something like the following:
In initWithStyle:reuseIdentifier
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
self.customView = [[UIView alloc] initWithFrame:CGRectZero];
}
return self;
}
Then override layoutSubviews to actually set the views frame
- (void)layoutSubviews
{
[super layoutSubviews];
self.customView.frame = CGRectMake(x, y, width, height);
}
As far as the "expression is not assignable" warning, this is because you cannot just set a views origin without setting the height and width. Use:
view.frame = CGRectMake(x, y, width, height);
If you want to keep the same width and height without having to hard code it do something like
view.frame = CGRectMake(x, y, view.frame.size.width, view.frame.size.height);

Resources