Is it possible to have an Auto Layout constraint (NSLayoutConstraint) with a dynamic constant/multiplier?
For example, this would be a bog standard NSLayoutConstraint:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:obj1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:obj2 attribute:NSLayoutAttributeLeft multiplier:1 constant:0];
Then here would be an alteration of that constraint but a dynamic variable in the constant:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:obj1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:obj2 attribute:NSLayoutAttributeLeft multiplier:1 constant:scrollView.contentOffset.x];
The second one would take the contentOffset of the scrollview and use it as the constant. However, having tried this, it only uses the offset which exists when the constraint is made.
What I would want it to have the constraint update the constant, when the scrollview if scrolling. This way it would keep using the most up to date contentOffset.
Is this possible?
Yes definitely.
In fact, that is what they are built for. When you are animating views etc... you need to be able to change the constraints.
Counter-intuitively the only property of NSLayoutConstraint that is writable is the constant property. (lol)
You are not quite right in your code though.
Creating the constraint you would first create a property for it...
#property (nonatomic, strong) NSLayoutConstraint *leftConstraint;
Then create it...
self.leftConstraint = [NSLayoutConstraint constraintWithItem:obj1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:obj2 attribute:NSLayoutAttributeLeft multiplier:1 constant:0];
Then edit the already existing constraint...
self.leftConstraint.constant = scrollView.contentOffset.x;
Then you need to force the view to relayout the subviews...
[self.view layoutIfNeeded];
Related
I have a view controller set up in a storyboard. The view controller's view contains a subview(UITableView) with pinned edges to the 4 sides of its parent, essentially making the view fill its parent.
I am adding the view controller's main view as a subview of another view controller's view like this:
UIView *overlayView = firstViewController.view;
[overlayView setTranslatesAutoresizingMaskIntoConstraints:NO];
UIView *sourceView = secondViewController.view;
[sourceView addSubview:overlayView];
NSLayoutConstraint *constraint;
constraint = [NSLayoutConstraint constraintWithItem:overlayView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:sourceView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0];
[sourceView addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:sourceView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:overlayView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:100];
[sourceView addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:overlayView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:sourceView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
[sourceView addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:overlayView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:sourceView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[sourceView addConstraint:constraint];
[sourceView layoutIfNeeded];
I want to have a gap from the right edge of the view to its parent equal to 100 pixels.
Now weirdly enough this works as expected on iOS 8, but fails to do so on iOS 7 and the view is displayed full-screen ignoring the 100 constant set on the trailing constraint. Also, if the view controller's view that is being added has no child views - then it is working as expected. Is setTranslatesAutoresizingMaskIntoConstraints: being applied to all the subviews of a view in iOS 7 ? What might be the reason for this ?
EDIT:
The issue lies somewhere in the way subviews are being managed by the OS.
When the Container View is set as a outlet to the view property of the view controller, things don't work. If however, I set the Table View as an outlet to the view property, the it works. Something weird happens if there is a child view with pinned edges to its superview and then I am adding other constraints to the superview. I dont understand why it works fine on iOS 8 though...
EDIT 2
The problem seems to happen only with the trailing constraint. If I want to modify the constants of any of the other constraints there are no issues ?!
Can you try interchanging sourceView with overlayView in your constraint?
`constraint = [NSLayoutConstraint constraintWithItem:sourceView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:overlayView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:100];`
This essentially means that sourceView.trailing - 100 = overlayView.trailing; which is not what you would want.
Edit 1:
Is setTranslatesAutoresizingMaskIntoConstraints: being applied to all the subviews of a view in iOS 7?
NO. translatesAutoresizingMaskIntoConstraints is set only for the views you explicitly call setTranslatesAutoresizingMaskIntoConstraints for.
I'm trying to make constraints programmatically. I have textField and button created in IB. Here is the code:
UIView *superview = self.view;
self.button.translatesAutoresizingMaskIntoConstraints = NO;
self.textField.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.textField
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeCenterY
multiplier:1.0f
constant:0];
[superview addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:self.textField
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeCenterX
multiplier:1.0f
constant:0];
[superview addConstraint:constraint];
constraint =[NSLayoutConstraint constraintWithItem:self.button
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.textField
attribute:NSLayoutAttributeLeading
multiplier:1
constant:-10];
[superview addConstraint:constraint];
constraint =[NSLayoutConstraint constraintWithItem:self.button
attribute:NSLayoutAttributeBaseline
relatedBy:NSLayoutRelationEqual
toItem:self.textField
attribute:NSLayoutAttributeBaseline
multiplier:1
constant:0];
[superview addConstraint:constraint];
And after running I have this issue:
Constraints also have no effect on views.
What am I doing wrong?
First in the storyboard editor select the view controller you want to change then, in the constraint editor, select "ADD missing constraints in view controller"
Select an elment that you'd like to modify at runtime by remomiving and adding new constraint, in the object inspector panel select each constraints and edit it by checking the box "placeholder: remove at build time".
This is a way to say to storyboard editor that you are fine with the constraint ant it should not apply or complain about an insufficient constraints situation.
Constraints flagged are removed at runtime, so you should provide you own constraint and is better if you do in the right place. Ovverride -updateViewConstraints in the view controller and remember to call super and add you new constraints.
You have three auto-generated constraints "IB auto generated at build time for view with fixed frame". These conflict with the ones you make yourself. Probably, you have set fixed size properties on the text field and button in Interface Builder. Possibly, you have a fixed width on your button, and that conflicts with the trailing constraint.
Combining IB with code constraints can be tricky. I have found it is often easier to design things only in IB or only in code to avoid conflicts like these, although combining the two is perfectly viable.
For a good explanation on what NSIBPrototypingLayoutConstraint is, have a look at this SO answer:
Trouble with AutoLayout on UITableViewCell
I have a web page that start with a width constrain of 100.
When the user click a button i want to change the constrain to : 200.
I tried this:
NSLayoutConstraint *constrain = [NSLayoutConstraint
constraintWithItem:self.webPage
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.webPage
attribute:NSLayoutAttributeWidth
multiplier:1
constant:100];
[self.webPage addConstraint:constrain];
But this throws out this exception :
"Unable to simultaneously satisfy constraints."
Any ideas?
You have two options.
Get a reference to the original constraint and change the constant part to 200
Get a reference to the original constraint and remove it from the view, and add the new constraint
I would go for the first option. To get a reference add a #property for the constraint to your viewController and assign it when you create it.
If you are creating the constraint in a xib or storyboard connect the constraint with a IBOutlet connection to your code, similar to what you do when you connect a UILabel.
You can then easily adjust the constant part of the constraint.
Also you constraint should probably be more along these lines:
NSLayoutConstraint *constraint = [NSLayoutConstraint
constraintWithItem:self.webPage
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:100];
if you want to set the width don't have a toItem: set.
_myConstrain = [NSLayoutConstraint
constraintWithItem:self.webPage
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:100];
// add to superview! not to self.webPage
[self.view addConstraint:_myConstrain];
When you want to change it later:
_myConstrain.constant = 200.0f;
[self.view layoutIfNeeded]; // you may be able to call this on self.webPage
I am building an app with a modal view containing a UICollectionView and below it a view containing two buttons (validate/cancel).
The number of rows in my UICollectionView can change depending on the data and don't know it beforehand so I want to add a constraint to always keep my buttons 30px below the collectionView.
I am not using auto-layout for this as I have some animations which work better without it so I don't know how to programmatically add such constraints.
Does anyone have any idea how to do it?
Many thanks for your help
I am not sure about the collection view. However, the below works with a standard view and since UICollectionView is a type of UIView, then the code might work with collection view also
NSLayoutConstraint *bottomConstraint=[NSLayoutConstraint constraintWithItem:buttonA attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:0.45 constant:0];
NSLayoutConstraint *widthConstraint=[NSLayoutConstraint constraintWithItem:buttonA attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:95];
NSLayoutConstraint *heightConstraint=[NSLayoutConstraint constraintWithItem:buttonA attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:95];
NSLayoutConstraint *leftConstraint=[NSLayoutConstraint constraintWithItem:buttonA attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:20];
[self.view addConstraints:#[bottomConstraint,widthConstraint,heightConstraint,leftConstraint]];
The above basically lays out the buttonA relative to the view. However, to use this, the auto layout should be off (as in your case).
The bottom Constraint says that this constraint is related to button A and will act on its attribute NSLayoutAttributeBottom(bottom side) and the bottom side will be present on exactly 0.45*(Bottom of view).
The width constraint specifies that it is related to button A and will act on its width. The width is not dependent on any other object (toItem is nil) and it will have a constant value of 95.
I want to be able to position my child view 25% the size of the super view from the top.
NSLayoutConstraint *topPositionConstraint = [NSLayoutConstraint constraintWithItem:_containerView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_childView
attribute:NSLayoutAttributeHeight
multiplier:0.25f
constant:0.0f];
However, right now I'm getting the following exception:
'NSInvalidArgumentException', reason: '*** +[NSLayoutConstraint constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:]: Invalid pairing of layout attributes'
Why does the error occur and how can I achieve what I want?
Instead of using the frame as in the accepted answer, you can move the percentage to the multiplier if you use the bottom instead of height. I'm using this technique for percentage based positioning of child views. It is also nice because the frame of your container view may not be set when you're creating the constraint:
NSLayoutConstraint *topPositionConstraint =
[NSLayoutConstraint constraintWithItem:_childView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_containerView
attribute:NSLayoutAttributeBottom
multiplier:0.25
constant:0];
You can't use top and height in the same constraint. Although it makes sense to say it the system doesn't like it.
What you could do instead is something like...
NSLayoutConstraint *topPositionConstraint =
[NSLayoutConstraint constraintWithItem:_childView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_containerView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:_containerView.frame.size.height * 0.25];