NSLayoutConstraints with NSLayoutAttributeTrailing not as expected - ios

I have an array with two new constraints.
When I set these constraints the button is placed 20 and 90 pix from the center of the superview.
I want the button 20px from the bottom and 90px from the right of the superview.
What am I doing wrong?
I create an array with two constraints:
NSLayoutConstraint *bottomTrailingConstraint = [NSLayoutConstraint constraintWithItem:_gameSettingsButton
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:_gameSettingsButton.superview
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:90.0f];
NSLayoutConstraint *bottomSpaceConstraint = [NSLayoutConstraint constraintWithItem:_gameSettingsButton
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:_gameSettingsButton.superview
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:20.0f];
_bottomConstraintsArray = #[bottomTrailingConstraint, bottomSpaceConstraint];
I remove the old constraints and then add the new constraints:
NSArray *oldconstraints = _gameSettingsButton.constraints;
[_gameSettingsButton.superview removeConstraints: oldconstraints];
[_gameSettingsButton.superview addConstraints:_bottomConstraintsArray];
I also tried: [_gameSettingsButton.superview updateConstraints] but that changes nothing.
Any help would be greatly appreciated.

This:
NSLayoutConstraint *bottomTrailingConstraint = [NSLayoutConstraint constraintWithItem:_gameSettingsButton
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:_gameSettingsButton.superview
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:90.0f];
means:
_gameSettingsButton.trailing == _gameSettingsButton.superview.trailing * 1 + 90
That will make the button's trailing edge outside of the superview. Assuming left-to-right layout (so "trailing" means "right"), the button's right edge will be 90 points to the right of the superview.
Similarly, this:
NSLayoutConstraint *bottomSpaceConstraint = [NSLayoutConstraint constraintWithItem:_gameSettingsButton
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:_gameSettingsButton.superview
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:20.0f];
means:
_gameSettingsButton.bottom == _gameSettingsButton.superview.bottom * 1 + 20
Again, this will put the button outside of the superview. The button's bottom edge will be down 20 points from the bottom of the superview.
You either want to swap the first and second items or you want to negate the constant.
Also, you are querying the constraints from the button and then removing those constraints from the superview. Well, since the constraints you queried are not on the superview, that does nothing. (In fact, the result of your query is probably an empty array since nothing ever adds any constraints to the button itself.) You probably meant to query the constraints on the superview but I suspect there will be constraints unrelated to the button which you don't want to remove. So, you will need to remember the constraints you set up for the button (probably in an instance variable) and remove those. If the initial constraints were set up in IB, then you'll need to set up an outlet to track them.

Related

iOS Autolayout same width constraint

I've got a layout which I want to look like this:
Location: "blabla"
Website: "blabla"
LongerLabel: "blabla"
I want all the labels to be the same width. Hardcoded this can look like this:
#"H:|[locationLabel(80)]-5-[location]|"
#"H:|[websiteLabel(80)]-5-[website]|"
#"H:|[remarksLabel(80)]-5-[remarks]|"
I've already tried this using circular reference which doesn't work ;)
#"H:|[locationLabel(websiteLabel)]-5-[location]|"
#"H:|[websiteLabel(remarksLabel)]-5-[website]|"
#"H:|[remarksLabel(locationLabel)]-5-[remarks]|"
I do not want my labels to be hardcoded to 80, but I want them all the same width according to the max intrinsic content size.
You can do it using circular greater-than-or-equal-width constraints:
#"H:|[locationLabel(>=websiteLabel)]-5-[location]|"
#"H:|[websiteLabel(>=remarksLabel)]-5-[website]|"
#"H:|[remarksLabel(>=locationLabel)]-5-[remarks]|"
(sorry for weird colors :) )
UPDATE: I've just checked simple "equal" circular constraints, and they work in my case as well. However, I think, "equal" constraints don't specify the "main" label in any way, they just state, that labels should be equal. And if they are all equal to the smallest label, it is also fine. So in the "equal" case it probably depends on the order of constraints.
Now let's take a look at two hypothetical labels ("Label" and "Label label" (LL for shortness)), which are connected using ">=" constraints.
Warning: the following text is just an assumption, I've never seen an actual autolayout implementation. I just know that it uses a complicated linear equations solving system to find a solution which satisfies all constraints as close as possible.
The autolayout engine (AE) takes the first label and makes it as small as possible because of the content hugging. The LL is ignored for now. Then the AE takes the second label. In case of equal constraints it would have to make it short, like the first labels, because the first label has already been processed. But in case of ">=" constraint the AE can make the second label longer. However, now it affects the "L >= LL" constraint and AE has to switch back to L and process it one more time using the new data (in case of "==" constraint it would stop already, because no conflicts emerged). The only solution now is to make the first label longer, which AE does, because it doesn't introduce any conflicts.
So, this way in several iterations the AE ends up with all labels being the same width (the width of the longest label).
Yes, you can specify fix-same width constraints using Visual Formatting language.
Constraints will be like below:
#"H:|[locationLabel]-5-[location]|"
#"H:|[websiteLabel]-5-[website]|"
#"H:|[remarksLabel]-5-[remarks]|"
Below additional constraints are required:
// Width constraints for Left side labels
NSLayoutConstraint *locationLabelConstraint = [NSLayoutConstraint constraintWithItem:locationLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:0.6 constant:0];
[self.view addConstraint:locationLabelConstraint];
NSLayoutConstraint *websiteLabelConstraint = [NSLayoutConstraint constraintWithItem:locationLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:0.6 constant:0];
[self.view addConstraint:locationLabelConstraint];
NSLayoutConstraint *remarksLabelConstraint = [NSLayoutConstraint constraintWithItem:locationLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:0.6 constant:0];
[self.view addConstraint:remarksLabelConstraint];
// Width constraints for right side labels
NSLayoutConstraint *locationConstraint = [NSLayoutConstraint constraintWithItem:locationLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:0.3 constant:0];
[self.view addConstraint:locationConstraint];
NSLayoutConstraint *websiteConstraint = [NSLayoutConstraint constraintWithItem:locationLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:0.3 constant:0];
[self.view addConstraint:websiteConstraint];
NSLayoutConstraint *remarksLabelConstraint = [NSLayoutConstraint constraintWithItem:locationLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:0.3 constant:0];
[self.view addConstraint:remarksConstraint];
Explanation:
Above all constraints are width constraints equally related to its parent view width, these means labels will take all full width of its parent view. Here you can change multiplier to fix labels width.
Multiplier = 1 => Full view width
Multiplier = 0.5 => Half view width
Observe left side label width constraint where multiplier = 0.6 which means that it will take 0.6% space of self.view width.
Same way right side label width multiplier = 0.3 which takes 0.3% space of self.view. Change these values as your convenience.
Note: Also please check Xcode console sometimes there constraints gives warnings about constraint conflict. If there is any warning please resolve by changing constraints properties.
Not sure if I am missing something, but simple 'equal width' constraints should have sufficed (no need for >=). They did for me.
The default compression resistance priority (750) is greater than the default content hugging priority (250). So it all works out in the end.

Update constraints programmatically?

I have a group of views in my view controller, each set with a constraint of 15 from the previous. When I press a button elsewhere on the VC, I have one of the views, near the top, double in height. How do I get the rest of the views to snap to their new constraints?
NSLayoutConstraint *sample = [NSLayoutConstraint constraintWithItem:yourTargetView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:yourTargetsParentView
attribute:NSLayoutAttributeLeft
multiplier:0.00
constant:100];
[yourTargetsParentView addConstraint:sample];

View is not getting stretched to edges of phone on landscape orientation? How to add constraints programmatically?

How to add constraints programmatically for views which should be placed next to each other on scroll view?
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view2 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:view1 attribute:NSLayoutAttributeRight multiplier:1 constant:10];
[self.scrollView addConstraint:constraint];
constraints are basically a equation iOS will try to satisfy at runtime.
General form is:
item1.attribute = multiplier * (item2.attribute) + constant
In the code above:
view2.left = 1 * (view1.right) + 10
So view2's left will be at 10pt space from view1's right.

Auto Layout - programatically defining unusual constraints

I'm trying to find examples for some relatively nonstandard layouts, but all the documentation I found only gave examples of centring or aligning with the superview borders, or giving fixed numbers as spaces and sizes.
Can you please advise about programatically defining the following constraints:
A. Diagonal views:
B. Put a view on the third of it's superview's border (or some other ratio which is not the middle)
C. Two standard spaces from the superview's border (like this |-[v1], but with double space)
Any documentation that discusses similar cases would be also highly appreciated :)
Thanks in advance!
You can use the verbose NSLayoutContraint method to create any constraint, that can't be expressed with the visual format language.
A: You need two constraint that describe exactly what you want. In this case you want the top edge of v1 and the bottom edge of v2 be the same. Similarly for the right and left edges.
NSLayoutConstraint *constraint;
constraint = [NSLayoutConstraint constraintWithItem:v1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:v2
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0];
[self.view addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:v1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:v2
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0.0];
[self.view addConstraint:constraint];
B: This time you want to top edge of v1 and v2 to be the same:
NSLayoutConstraint *constraint;
constraint = [NSLayoutConstraint constraintWithItem:v2
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:v1
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
[self.view addConstraint:constraint];
What constraint to use for the horizontal position of the orange view, depends on what you want. Do you want a fixed margin to the right edge of the blue view?
constraint = [NSLayoutConstraint constraintWithItem:v2
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:v1
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:rightMargin];
[self.view addConstraint:constraint];
C: I don't think you can specify a double space like that. You could either use an ugly trick, and insert an invisible (width 0) view between the standard spaces like this:
|-[invisibleView(0)-[v1]
Or you could just figure out what the standard margin is, and use twice that value as a constant:
|-(doubleMargin)-[v1]

How to set two different layouts for one view using constraints?

I will be short and very clear. I want to do what's on the figure below using constraints.
Any suggestions or solutions?
Description:
The coloured places are UIViews, containing for ex. 4 labels. So what constraint should I use to manipulate with the second UIView so in Portrait mode to be under the first one and in Landscape to be next to it?
The code below assumes you already have a reference to the orange view and the yellow view in your code. When in portrait mode you want them to be in sequence so you can have a layout as such
NSLayoutConstraint *portraitConstraint = [NSLayoutConstraint
constraintWithItem:orangeView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:yellowView
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:2.0f];
[self.view addConstraint:portraitConstraint];
When in landscape mode you can have a layout constraint as such
NSLayoutConstraint *landscapeConstraint = [NSLayoutConstraint
constraintWithItem:orangeView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:yellowView
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:2.0f];
[self.view addConstraint:landscapeConstraint];
Now these are not the full list of constraints you would need, if you build in the code and only the code you would have to have the orange view stick to the top, leading and trailing of the view and then in the code have the yellow view stick to the leading, trailing and bottom in the view for portrait.
In landscape you would have the orange view stick to the top, bottom and leading while the yellow view would stick to the top, bottom and trailing.
The constraints that are above would allow so you do not need a height to be set, but you might want to also say something like the orange view bottom is centerY - 1.0 in portrait and centerX - 1.0f in landscape thus avoiding the need for widths and heights and hence not worrying about the size of the screen. Center X and Y are below
NSLayoutConstraint *centerX = [NSLayoutConstraint
constraintWithItem:orangeView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0f
constant:-1.0f];
[self.view addConstraint:centerX];
NSLayoutConstraint *centerY = [NSLayoutConstraint
constraintWithItem:orangeView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0f
constant:-1.0f];
[self.view addConstraint:centerY];
The above constraints should help you on your way to resolving the issue.

Resources