Understanding visual format constraints in iOS Objective C - ios

I know about setting constraint in InterfaceBuilder ex. Leading, trailing, top, bottom, fixed width etc..
I found some constraint code, I don't know what this code trying to set which constraint, What is exactly meaning of below visual format constraints?
NSDictionary *binding = #{#"v" : self.view};
NSDictionary *metrics = #{#"height" : #(self.height)};
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[v]|" options:0 metrics:nil views:binding]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[v(==height)]|" options:0 metrics:metrics views:binding]];

H:|[v]|
H represents that the constraints are meant to be added horizontally, similarly V is for vertical.
| represents super view as indicated by the binding dictionary. NSDictionary *binding
[v] represents the view itself.
So H:|[v]| resolves to leading & trailing constraints with 0 constant.
V:[v(==height)]|
Similarly here, view is given a bottom constraint and a height constraint with a constant height as mentioned in NSDictionary *metrics.
Please refer https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html for more information.

As GoodSp33d suggested to me.
Your constraint is-
(1) leading & trailing to self.view is 0
I have converted above constraint into diffrent way
(2) ContentView's bottom assigned to self.view
(3)Constant height constraint
Constraint in another form as-
[self.contentView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
[self.contentView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES;
[self.contentView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:self.height];
[self.view addConstraint:heightConstraint];

Related

How to remove Equal Width constraint from UIButton Programmatically in iOS

I have two button with equal width constraints. I want to remove equal width constraint and add a new width constraint for one button. the other button constraint to zero.
This is what I've tried. But it's not working. The Equal width constraint is not removing
NSLayoutConstraint * constraint = [self
constraintWithIndientifer:#"MyButtonWidth" InView:self.view];
[self.view removeConstraint:constraint];
NSLayoutConstraint * newconstraint = [NSLayoutConstraint constraintWithItem:self.departureButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1 constant:self.view.frame.size.width];
newconstraint.identifier = #"MyButtonWidth";
[self.departureButton addConstraint:newconstraint];
[self.view layoutIfNeeded];
-(NSLayoutConstraint *)constraintWithIndientifer:(NSString *)identifer InView:(UIView *)view{
NSLayoutConstraint * constraintToFind = nil;
for (NSLayoutConstraint * constraint in view.constraints ) {
if([constraint.identifier isEqualToString:identifer]){
constraintToFind = constraint;
break;
}
}
return constraintToFind;
}
You don't need to do this,
you can add another constraint such as width constraint to >=1
after that, you should set "equal constraint" "priority to 1
after that, you should set "width constraint" priority to 2 or more(default is 1000)
after that, when you set constraint constant it works and equal constraint is not working because of priority:
self.yourButtonWidthConstraint.constant = yourWidth;
If you look at the documentation for NSLayoutConstraint, you'll find an isActive property:
You can activate or deactivate a constraint by changing this property. Note that only active constraints affect the calculated layout.
Activating or deactivating the constraint calls addConstraint: and removeConstraint: on the view that is the closest common ancestor of the items managed by this constraint. Use this property instead of calling addConstraint: or removeConstraint: directly.
So going by that I'm changing both the removeConstraint and addConstraint you have in your sample code.
aka just change your code to this:
NSLayoutConstraint * constraint = [self
constraintWithIndientifer:#"MyButtonWidth" InView:self.view];
constraint.active = NO; // CHANGE 1
NSLayoutConstraint * newconstraint = [NSLayoutConstraint constraintWithItem:self.departureButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1 constant:self.view.frame.size.width];
newconstraint.identifier = #"MyButtonWidth";
newConstraint.active = YES; // CHANGE 2
[self.view layoutIfNeeded];
NOTE: the NSLayoutConstraint class also has two class funcs: + activateConstraints: and + deactivateConstraints:, which both take an array of constraints. That's preferred when you're changing multiple constraints, but not needed here since its only 1.
Loop though the constraints of the parent view of these two buttons and delete the constraint where first item is the first button and second is the second button and layoutAttribute is width
for (NSLayoutConstraint*co in self.parentOfBtns.constraints)
{
if(co.firstItem==self.btn1&&co.secondItem==self.btn2&&co.firstAttribute==NSLayoutAttributeWidth&co.secondAttribute==NSLayoutAttributeWidth)
[self.parentOfBtns removeConstraint:co];
}
then add your new width constraint , and don't forget to call
[self.view layoutIfNeeded];

How to add width constrain using constraintsWithVisualFormat while already using constraintsWithVisualFormat for vertical spacing between two view?

I am new to IOS and I am adding constrain to my added view with using constraintsWithVisualFormat. I already use constraintsWithVisualFormat to have vertical spacing by these following code:-
self.underlineConstraints = [NSLayoutConstraint
constraintsWithVisualFormat:#"V:[view]-80-[underlineView(5)]"
options:NSLayoutFormatAlignAllLeading | NSLayoutFormatAlignAllTrailing
metrics:nil views:views];
Here 5 is height of underlineView and 80 is space between two views but I want to add constrain of width of underlineView 150.
To add width constraints:
[self.view addConstraints: [NSLayoutConstraint
constraintsWithVisualFormat:#"H:[underlineView(150)]"
options: kNilOptions
metrics:nil views:views]];

Second view's size is ignored when constraining two views to fill the screen with visual format

I'm trying to set a constraint where the width of two views should fill the screen. If I change the width of one view, the other view should change accordingly:
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-8-[view1]-8-[view2]-8-|" options:0 metrics:nil views:views]];
I think this is rather simple, but the code seems to ignore the size of view2. view1 keeps getting the entire width of the screen and setting the frame of view2 doesn't affect the width of view1.
What am I missing?
Assign a minimum size constraint to view2 as well, or "0 width" will be a valid solution to the constraints.
[NSLayoutConstraint activateConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-8-[view1(>=minSize)]-8-[view2(>=minSize)]-8-|"
options:0
metrics:#{#"minSize" : #50}
views:views]];
Also, don't use addConstraints: anymore (iOS 8+). Use -[NSLayoutConstraint setActive:] or +[NSLayoutConstraint activateConstraints:]
As for changing view2's size, you can't set views' frames directly when you're using Auto Layout. The value will be overwritten on the next layout pass. You should create an explicit width constraint for the view:
[NSLayoutConstraint constraintWithItem:view2
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:120];
and then adjust that constraint's constant when you need to change the view's size.

Flexible width constraint

I got stuck with resolving constraints, perhaps someone can help:
Here is the case:
I need myView to have flexible width according to superview width.
If superview has more width than 500 -> myView should have 500.
If superview has less width then 500 -> myView should take all superView width.
[NSLayoutConstraint constraintsWithVisualFormat:#"[myView(==500)]" options:0 metrics:nil views:#{
#"myView" : self.myView,
#"superview" : superview
}];
// If I write myView(<=500), obviously width will be zero.
// I can not add something like this:
// #[tableView(<=superview)]", as width can be less, can be more
So I got stuck here, any thoughts?
Thanks in advance!
Visual Format Language can be kind wonky even by Auto Layout standards, especially when setting priority. The problem is actually conceptual and can be done in IB or with VFL or by the class methods. Anyway, the issue:
When you want some behavior to change in a set way when some condition is met or not met, try switching to priority instead of inequality. In this case, think of the margins: You want to the horizontal margin to be 0 when the total width is under 500, but adding space when more than 500. Constraints you will need:
Required priority 1000: Set myView to less than or equal to 500.
Priority 999: Set myView attached to superview with leading/trailing space=0.
If the space is less than 500, both conditions can be met. If the space is more than 500, than it will start breaking constraints, starting with the lowest priority.
Note that after breaking the margin constraints it won't know where to set the horizontal x placement of myView, so you will need another constraint. Centering in superview will conflict the least, since it can place it horizontally with or without margin constraints broken. There is a way to get it to add space on one side only, but it's a complicated dance-of-the-breaking-constriants leading to messy required programmatic intervention; I think centered-horizontally gets your intended behavior.
You need more than one constraint and the use of priorities. One constraint for myView to equal the superview width as you already have and another for the myView to be NSLessThanOrEqual to 500 as suggested above. Then set the priority of the latter to be greater than the equal width constraint. I think that should be enough for the width. Maybe you'll need a 3rd constraint with lower priority to set the width of myView to 500 if it still shrinks to 0 to avoid ambiguity.
Here's an edit to my answer with some code to illustrate better:
Using visual layout as below allows for the more readable layout (once you're used to the syntax)
NSDictionary *views = #{#"myView" : _myView, #"superView": self.view};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-100-[myView(40)]" options:0
metrics:nil
views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(3#999,>=3)-[myView(<=500)]-(3#999,>=3)-|" options:0
metrics:nil
views:views]];
If put a padding of 3 just to illustrate the shrinking to the edges.
If you wanting to center myView in the superview visual layout will be lacking and you'll need to add a long form constraint like this:
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.myView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
But all of this could done in Storyboards without any code based on the same logic. If not using Storyboards I try to use the visual layout as much as possible and usually only the long form for centering views or when dynamically laying views where the structure is only known at runtime.
As per your question you need to set constraint to myView. Here's what you need to do:
UIView *myView = [UIView new];
myView.translatesAutoresizingMaskIntoConstraints = NO;
myView.backgroundColor = [UIColor blueColor];
[self.view addSubview:myView];
NSDictionary *viewDictionary = #{#"myView":myView};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-50-[myView(200)]" options:0 metrics:0 views:viewDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-10-[myView(<=500)]" options:0 metrics:0 views:viewDictionary]];
NSLayoutConstraint *tailing = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:myView attribute:NSLayoutAttributeTrailingMargin multiplier:1 constant:75];
tailing.priority = 999;
[self.view addConstraint:tailing];
Result:
Hope this might help in solving your problem.
You can visit this link for setting auto layout constraints programmatically for advance help.
ref: http://technet.weblineindia.com/mobile/ui-design-of-ios-apps-with-autolayout-using-constraints-programmatically/
ref: https://codehappily.wordpress.com/2013/10/09/ios-how-to-programmatically-add-auto-layout-constraints-for-a-view-that-will-fit-its-superview/

Auto Layout defining constraints

I have two buttons on my UIView. I want the horizontal distance between these two buttons should, be the 20% width of the superview?
Any help about this?
You can accomplish this using an empty dummy view in between the two buttons, whose width is 20 % of the width of the superview.
In code, it would look something like this:
// The important thing here is that the buttons are flush against the spacer
[superview addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"[button1]-0-[spacer]-0-[button2]"
options:0
metrics:nil
views:#{#"button1" : button1, #"button2" : button2, #"spacer" : spacer}]];
// Here, we set the width of the spacer to 20% of the super view
[superview addConstraint:
[NSLayoutConstraint
constraintWithItem:spacer
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeWidth
multiplier:0.2
constant:0]];

Resources