Reduce a UIView's Height From the Top Down? - ios

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.

Related

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;

Rotate a UIlabel to make it vertical

I have a UILabel. I need to rotate it programmatically.
I have my horizontal UILabel, for example with frame: x:0, y:0, w: 200, h:80.
Now I would like to rotate the label to make it vertical:
I try this code:
[self setTransform:CGAffineTransformMakeRotation(M_PI_2 / 2)];
I can see the contained text rotated. But I would like to rotate the whole frame: With my code, the UILabel continues to have the same frame.
Try this working code:
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 100, 200, 80)];
//set background color to see if the frame is rotated
[label setBackgroundColor:[UIColor redColor]];
[label setText:#"Text Here"];
label.transform=CGAffineTransformMakeRotation( ( 90 * M_PI ) / 180 );
[self.view addSubview:label];
Hope it helps
If you prefer set the label position and size visually in you xib or storyboard do the following:
Set the labels position and size in the interface builder like you want them to stay after the rotation.
Rotate the label and set the frame again:
-(void)rotateLabel:(UILabel*) label
{
CGRect orig = label.frame;
label.transform=CGAffineTransformMakeRotation(M_PI * 3/2);//270º
label.frame = orig;
}
Your Label is a square(w:100,h:100).So your label has transformed, but you can't see the change,because the width is equal to the height.

UIScrollView and UIImageView - move horizontally

I have UIScrollView with UIImageView on it. I set image in code and it can have different width (height is always the same). I want to do it scrollable (eg. see only part of it and drag it to left / right to see the rest).
I have this code performed after image change:
float x = self.imageView.frame.origin.x;
float y = self.imageView.frame.origin.y;
float w = scaleImg.size.width;
float h = scaleImg.size.height;
self.imageView.frame = CGRectMake(x, y, w, h);
self.imageView.bounds = CGRectMake(0, 0, w, h);
w = self.frame.size.width;
h = self.scrollView.frame.size.height;
[self.scrollView setContentSize: CGSizeMake(w + 20, h)];
Problem is, that it didn't scroll correctly (I think it didn't scroll at all). And after I scroll, my image is resized, not respecting the frame I have set above.
I am using iOS7, storyboard, autolayout.
If you want the image to fit the scrollview and be scrollable left and right only you should create the image view with height same as the scrollview and width scaled accordingly to the image. Try something like this:
UIImage *image;
UIScrollView *scrollView;
UIImageView *imageView;
imageView = [[UIImageView alloc] initWithFrame:CGRectMake(.0f,
.0f,
scrollView.frame.size.height*image.size.width/image.size.height,
scrollView.frame.size.height)];//fixed height
imageView.image = image;
imageView.contentMode = UIViewContentModeScaleToFill;
[scrollView addSubview:imageView];
scrollView.contentSize = imageView.frame.size;
scrollView.contentOffset = CGPointMake((scrollView.contentSize.width-scrollView.frame.size.width)*.5f, .0f);//scroll to center
Note I designed this only for images which have proportionally larger width then scrollview. If that is not always the case you should create a specific logic for other images (either background is visible or it is scrollable in Y coordinate).
I guess you should remove the following lines:
w = self.frame.size.width;
h = self.scrollView.frame.size.height;
scrollView's content size should reflect size of enclosed content, i.e. it should be scaleImg.size.width & scaleImg.size.height.
you given the w as the width for both scrollview and imageview....content size should be more than scroll view.Try giving imageview width more than scrollview.

UIImageView and autolayout

I have a view that is set up nicely using autolayout. The view contains a series of labels stacked from top to bottom. I am allowing the intrinsic size of these labels to determine the size of the view.
The final step is to add a background from an image. I started by trying the colorWithPatternImage method on UIColor but this isn't quite what I am looking for. I do not want to tile the image, and I can not guarantee it will always be larger than the intrinsic size of the view.
Similarly, adding a uiImageView to the view itself doesn't quite work. The view will expand to accommodate the image when I want to keep the intrinsic size based on the labels.
I guess what I am looking for is the following.
1) The background should have no effect on the size of the view.
2) The image should be scaled to fill the view but in it's original aspect ration (so cropping edges if necessary).
Any ideas appreciated.
In my case, I needed it for a UIImageView inside a dynamically-sized view in a UITableViewCell, but the image refused to shrink below its instristic size and instead worked as a minimum-size constraint for the superview. The only way I could get it ignore the intristic size is by lowering the priority at which it is enforced, right after creating the cell:
[imageView setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
forAxis:UILayoutConstraintAxisHorizontal];
[imageView setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
forAxis:UILayoutConstraintAxisVertical];
After this, all my constraints magically started working. In the OP's case, setting UIViewContentModeScaleAspectFill is also required, as per Mundi's answer.
In Interface Builder, add a UIImageView as the first subview to the view. Make sure its size always matches the view.
Then, in Interface Builder or code, set the contentMode:
backgroundImageView.contentMode = UIViewContentModeScaleAspectFill;
Here's how I would approach this. Hopefully it helps. :)
CGRect contentFrame = CGRectMake(0, 0, self.view.frame.size.width, 0); // This will be the frame used to create the background image view.
UIEdgeInsets contentInsets = UIEdgeInsetsMake(20, 20, 20, 20); // The margins by which the labels will be inset from the edge of their parent view.
CGFloat labelHeight = 21;
CGFloat verticalGap = 8; // The vertical space between labels
CGFloat y = contentInsets.top;
int numberOfLabels = 10;
for (int i = 0; i < numberOfLabels; i++) {
CGRect frame = CGRectMake(contentInsets.left, y, self.view.frame.size.width - (contentInsets.left + contentInsets.right), labelHeight);
UILabel *label = [[[UILabel alloc] initWithFrame: frame] autorelease];
// customize the label here
[self.view addSubview: label];
contentFrame = CGRectUnion(contentFrame, label.frame);
y += labelHeight + verticalGap;
}
contentFrame.size.height += contentInsets.bottom;
UIImageView *backgroundImageView = [[[UIImageView alloc] initWithFrame: contentFrame] autorelease];
[backgroundImageView setClipsToBounds: YES];
[backgroundImageView setContentMode: UIViewContentModeScaleAspectFill];
[backgroundImageView setImage: [UIImage imageNamed: #"background_image.png"]];
[self.view insertSubview: backgroundImageView atIndex: 0];

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

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.

Resources