Using Auto Layout to change spaces equally on resizing - ios

If I have a view in my app with 3 subviews (as an example) with the following spacing between them:
TOP - 40 points - SUBVIEW 1 - 60 points - SUBVIEW 2 - 80 points - SUBVIEW 3 - 80 points - BOTTOM
I understand how I can use auto layout to ensure each of my subviews keeps it's height and width, and I can get everything to be aligned on either a 3.5 OR 4 inch iPhone screen.
But I can't work out what kind of constraint I need to make it so that if it were aligned for a 3.5" screen, and then went to a 4" screen, each spacing would increase proportionally (e.g. 40 points would go to 47, 60 to 71, 80 to 95 - or thereabouts).
Is this possible? Or do I need to make all the spacing equal between the elements? (If so how would I still get it to resize equally?)
I'm new to Auto Layout so if I've missed anything off, or haven't made it clear what I means please let me know, thank you.

The Trick is to add not only one but TWO constraints between your views. One with "Greater Than or Equal" and one with "Less Then or Equal".
The minimum size (Greater Then or Equal) should be the spacing on the 3.5" display.
The maximum size (Less Then or Equal" should be the spacing on the 4" display.
Example:
TOP - 40 points - SUBVIEW 1 - 60 points - SUBVIEW 2 - 80 points - SUBVIEW 3 - 80 points - BOTTOM
TOP - SUBVIEW1:
Select both in Interface Builder. Add the constraint "Vertical Spacing" two times.
Set one to Greater Then or Equal 40.
Set the other to Lesser Then or Equal 47.
The sum of all Greater Then values + all heights of your views should be 480 pixel (3.5")
The sum of all Lesser Then values + all heights of your views should be 568 pixel (4")

I don't know any easy way to do this. I made one where all the spaces were equal, but to do that, I had to fill the spaces with invisible labels (just label with no title). So, I had my 4 visible objects and the 5 "spacer labels" all right on top of each other. I made the 4 visible objects all have explicit heights, and the spacers with no fixed heights, but set to be all the same:
-(void)viewDidLoad {
[super viewDidLoad];
NSMutableDictionary *viewsDict = [NSMutableDictionary dictionary];
for (int i=1; i<5; i++) { // Labels with titles
UILabel *b = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 150, 44)];
b.text = #"This is my label";
[b setTranslatesAutoresizingMaskIntoConstraints:NO];
[viewsDict setObject:b forKey:[NSString stringWithFormat:#"b%d",i]];
}
for (int i=1; i<6; i++) { // Spacer labels
UILabel *l = [[UILabel alloc ]init];
[l setTranslatesAutoresizingMaskIntoConstraints:NO];
[viewsDict setObject:l forKey:[NSString stringWithFormat:#"l%d",i]];
}
for (id obj in viewsDict.allKeys)
[self.view addSubview:viewsDict[obj]];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[l1][b1][l2(==l1)][b2][l3(==l1)][b3][l4(==l1)][b4][l5(==l1)]|"
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:viewsDict];
NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[b1]"
options:0
metrics:nil
views:viewsDict];
[self.view addConstraints:constraints];
[self.view addConstraints:constraints2];
}
To make the spaces different, I think you have to use the longer form of expressing constraints, rather than the visual format. The below code seems to work for me. I'm using the same definition of the views as above, except that I cut the number of titled labels to 3 and spacers to 4 to match you question. The relative spacing should be as in your example, 2:3:4:4.
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeTop relatedBy:0 toItem:viewsDict[#"l1"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:viewsDict[#"l1"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:viewsDict[#"b1"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:viewsDict[#"b1"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:viewsDict[#"l2"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *con4 = [NSLayoutConstraint constraintWithItem:viewsDict[#"l2"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:viewsDict[#"b2"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *con5 = [NSLayoutConstraint constraintWithItem:viewsDict[#"b2"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:viewsDict[#"l3"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *con6 = [NSLayoutConstraint constraintWithItem:viewsDict[#"l3"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:viewsDict[#"b3"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *con7 = [NSLayoutConstraint constraintWithItem:viewsDict[#"b3"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:viewsDict[#"l4"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *con8 = [NSLayoutConstraint constraintWithItem:viewsDict[#"l4"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
NSLayoutConstraint *con9 = [NSLayoutConstraint constraintWithItem:viewsDict[#"l1"] attribute:NSLayoutAttributeHeight relatedBy:0 toItem:viewsDict[#"l2"] attribute:NSLayoutAttributeHeight multiplier:.66 constant:0];
NSLayoutConstraint *con10 = [NSLayoutConstraint constraintWithItem:viewsDict[#"l1"] attribute:NSLayoutAttributeHeight relatedBy:0 toItem:viewsDict[#"l3"] attribute:NSLayoutAttributeHeight multiplier:.5 constant:0];
NSLayoutConstraint *con11 = [NSLayoutConstraint constraintWithItem:viewsDict[#"l1"] attribute:NSLayoutAttributeHeight relatedBy:0 toItem:viewsDict[#"l4"] attribute:NSLayoutAttributeHeight multiplier:.5 constant:0];
NSLayoutConstraint *con12 = [NSLayoutConstraint constraintWithItem:viewsDict[#"b1"] attribute:NSLayoutAttributeLeading relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:100];
NSLayoutConstraint *con13 = [NSLayoutConstraint constraintWithItem:viewsDict[#"b2"] attribute:NSLayoutAttributeLeading relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:100];
NSLayoutConstraint *con14 = [NSLayoutConstraint constraintWithItem:viewsDict[#"b3"] attribute:NSLayoutAttributeLeading relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:100];
NSArray *constraints = #[con1,con2,con3,con4,con5,con6,con7,con8,con9,con10,con11,con12,con13,con14];
[self.view addConstraints:constraints];

Related

Trailing constraint not applying when programmatically adding UIConstraints

I'm trying to add a subview to a view with constraints through code. I had some success but the trailing constraint seems to be completely ignored for whatever reason.
My code:
leading_const = 16.f;
trailing_const = 16.f;
top_const = 12.f;
bottom_const = 12.f;
insertView.translatesAutoresizingMaskIntoConstraints = NO;
[view addSubview:insertView];
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:insertView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:view
attribute:NSLayoutAttributeLeading
multiplier:1.f
constant:leading_const];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:insertView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:view
attribute:NSLayoutAttributeTrailing
multiplier:1.f
constant:trailing_const];
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:insertView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:view
attribute:NSLayoutAttributeTop
multiplier:1.f
constant:top_const];
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:insertView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeHeight
multiplier:1.f
constant:130.f];
[superView addConstraints:#[leading, trailing, top, height]];
Result:
Appreciate any guidance!
Your constraint is being applied but as you have set it to 16 it is going 16 points past the trailing edge of the view. You therefore should use a negative value for the constant instead.

How to get UISearchBar baseline position?

I'm subclassing UISearchBar and I want to add label that it's baseline will be aligned with UISearchTextField's baseline. I extract UISearchTextField from the searchbar and try to setup constraints like this:
- (void)addConstraintToBottomLabel {
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:self.bottomLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:self.bottomLabel attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0];
NSLayoutConstraint *baseline = [NSLayoutConstraint constraintWithItem:self.bottomLabel attribute:NSLayoutAttributeBaseline relatedBy:NSLayoutRelationEqual toItem:self.searchTextField attribute:NSLayoutAttributeBaseline multiplier:1.0 constant:0];
[self addConstraints:#[leading, trailing, baseline]];
}
And the result looks like this:
As you see, the labels aren't aligned.
For testing in constraints works well in other situation I align bottomLabel's baseline with textField's bottom and it works great. So where could be the problem?

How to make equal space b/w UIFields using auto-layouts?

I am a beginner with iOS auto-layouts and I am adding five labels on my view controller and so for every thing is OK. (Here five labels width and heights are constant.)
My main requirement is how to make equal horizontal spacing b/w that five labels. I can set middle label and left and right corner labels and they are perfect. But I don't understand how to add second left and second right labels, and how to make equal space B/W them, as like another labels?
My requirement is exact like below image, please help me.
My code:
#import "ViewController2.h"
#interface ViewController2 ()
{
UILabel * left1;
UILabel * left2;
UILabel * middle;
UILabel * right1;
UILabel * right2;
}
#end
#implementation ViewController2
- (void)viewDidLoad {
[super viewDidLoad];
left1 = [[UILabel alloc] init];
left1.backgroundColor = [UIColor grayColor];
left1.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:left1];
left2 = [[UILabel alloc] init];
left2.backgroundColor = [UIColor grayColor];
left2.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:left2];
middle = [[UILabel alloc] init];
middle.backgroundColor = [UIColor grayColor];
middle.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:middle];
right1 = [[UILabel alloc] init];
right1.backgroundColor = [UIColor grayColor];
right1.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:right1];
right2 = [[UILabel alloc] init];
right2.backgroundColor = [UIColor grayColor];
right2.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:right2];
//Applying autolayouts for middle lable
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:middle
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:100]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:middle
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:10]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:middle
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:50]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:middle
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:20]];
//Appying autolayouts for left1 labe1
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:left1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:100]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:left1
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:10]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:left1
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:50]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:left1
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:20]];
//Appying autolayouts for right1 labe1
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:right1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:100]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:right1
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:-10]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:right1
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:50]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:right1
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:20]];
}
#end
Here is steps for you (via IB)
Create your 5 labels
Setup left and right label constraints (for left - leading, top, width, height; for right - trailing, top, width height)
Center horisontally middle label (top constraint, width, height, center horisontally)
Here is some trick - add two container views between left and middle label + middle label and right label
setup constraints for this containers ( leading and trailing constraints + top + height) This containers will be flexible depend on screen size
after adding constraints it should looks like
The last step - place other labels into green container and setup constraints as for middle label ( it should be centered in container + add top , width , height constraint )
All constraints provided at the left size, so you can easily recreate it via code, if you need
Hope this helps
So I know your question was answered, but in iOS9 there are UIStackViews which are specifically built for this situation. Here's how to use them for future reference:
Shift + click and drag to select all your labels (note you don't have to size them)
Click the StackView button on the bottom right of the screen
Then select your StackView and set Alignment to "Fill", and Distribution to "Equal Spacing"
Then with your stackview still selected click the pin icon, and put 200 at the top, 10 and 10 for the sides, and 130 for the height. Then click add 4 constraints.
Finally click the triangle icon and select update frames.
Voila! You have your layout without having to use spacers!

Autolayout issue with constraintWithItem VS constraintsWithVisualFormat

I have pretty simple UI that works well with constraintsWithVisualFormat, I tried to replace that with constraintWithItem and for some reason it is not working. I don't know what's wrong here.
The literal sentence I understood is
Vertically, the contentView should fill the entire height of its
superview with no padding.
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(contentView);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[contentView]|"
options:0
metrics:nil
views:viewsDictionary];
[containerView addConstraints:constraints];
//Below is my alternative code NOT working, but this should work too?
NSLayoutConstraint *constraints =
[NSLayoutConstraint constraintWithItem:contentView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0];
[containerView addConstraint:constraints];
Your new code specifies the height, but not position. The visual format specification says nothing about the item’s height, but instead pins the top and bottom edges of the views to each other.
Instead of creating constraint for height, create two constraints, one for top margin, and one for bottom margin, pinning the edges of contentView to containerView.
You should change it from height to top. And also add similar constraint to bottom.
NSLayoutConstraint *constraints =
[NSLayoutConstraint constraintWithItem:contentView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0];
[containerView addConstraint:constraints];
This visual format:
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(contentView);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[contentView]|"
options:0
metrics:nil
views:viewsDictionary];
[containerView addConstraints:constraints];
should be replaced with two constraints:
NSLayoutConstraint *topConstraint =
[NSLayoutConstraint constraintWithItem:contentView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0];
[containerView addConstraint: topConstraint];
and
NSLayoutConstraint *bottomConstraint =
[NSLayoutConstraint constraintWithItem:contentView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0];
[containerView addConstraint:bottomConstraint];

How to center a spinner with autolayout?

I am using this spinner in a project: https://github.com/misterwell/MMMaterialDesignSpinner
How would I add it to the center of the screen with autolayouts so that when the device rotates, it still stays in the center of the screen?
You can add autolayout constraints programmatically. You can use 4 constraints - center x, center y, width and height:
[self.spinner setTranslatesAutoresizingMaskIntoConstraints:NO];
NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:self.spinner attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0f constant:0.0f];
NSLayoutConstraint *centerYConstraint = [NSLayoutConstraint constraintWithItem:self.spinner attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0f];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:self.spinner attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:kSpinnerWidth];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self.spinner attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:kSpinnerHeight];
[self.view addConstraints:#[ centerXConstraint, centerYConstraint, widthConstraint, heightConstraint]];
If you're using storyboards you could add the Horizontal Centre in Container and Vertical Centre in Container. To do this, click on the view in interface builder and look at the bottom right of the IB screen. Click on the first of 4 buttons and check the appropriate boxes.
If you're using code you could write spinner.center = self.view.center in viewDidLayoutSubviews().

Resources