AutoLayout: Centering two views in code causes constraint exception? - ios

Short version: Creating a NSLayoutAttributeCenterX constraint always causes a constraint failure. Why?
I have a UITextField subclass which behaves as a search textbox. You tap the box and a list of items appear, the items are filtered by what you type. Like this:
The UITextField subclass is responsible for creating and displaying the view of items. The center of the list should match the textfield's center. So, I set my constraint like this:
NSLayoutConstraint *hugCenter =
[NSLayoutConstraint constraintWithItem:self.searchContainerView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0];
This invariably leads to the error:
Will attempt to recover by breaking constraint
NSLayoutConstraint:0x9a5e1a0 UIView:0x9a5ba40.centerX ==
SearchTextField:0x7154a40.centerX
For testing I've made my scene as simple as possible -- 1 textfield on a view controller:
Why does this cause a constraint error?

Are you missing a setTranslatesAutoresizingMaskIntoConstraints: somewhere?

Related

iOS - replicating a constraint programmatically

I created this constraint in Interface Builder. Without it, the below textview expands upwards as its content grows, with it, the textview expands downwards as its content grows.
How do I create that constraint in programmatically?
Here is what I tried:
[self addConstraint:[NSLayoutConstraint
constraintWithItem:_textView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_internalScrollView //this is the parent view
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:0.0]];
but it has no material affect on anything.
The UITextView object I am using is from this library https://github.com/MatejBalantic/MBAutoGrowingTextView but that is a red herring to this question.
Here is what you need to do.
[_internalScrollView addConstraint:[NSLayoutConstraint
constraintWithItem:_textView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_internalScrollView //this is the parent view
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:300.0]]; // constant should be 300 as shown by you in screen shot
btw, the above screen shot shows you are making constraint with top layout guide and not with parent view of textView if that is the case then layout attributes should be changed in the above code according to your needs

iOS initialise view programmatically without constraining the position

Here is my problem, I have a scroll view scrollExerciseIndex that I use only as a scrolling bar, in this scroll view I place a UIView indexesView and I want it to be always at the center of the scroll view. For this I use layout constraints :
UIView * indexesView = [[UIView alloc] initWithFrame: CGRectMake(xPosition, 0, dimension*numberIndexes, dimension)];
[self.scrollExerciseIndex addSubview:indexesView];
[self.scrollExerciseIndex setContentSize:CGSizeMake(dimension*numberIndexes, dimension)];
if (xPosition != 0) {
NSLayoutConstraint * xCenterConstraint = [NSLayoutConstraint constraintWithItem:indexesView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.scrollExerciseIndex attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0];
[self.scrollExerciseIndex addConstraint:xCenterConstraint];
}
Here is the expected result :
Don't pay attention to all the element, just the bar at the bottom of the screen is my problem.
I have to create view programmatically because sometimes I will activate the constraints, sometimes not and I have to set the frame of the view dynamically. So for now I initialise the view indexesView like so :
UIView * indexesView = [[UIView alloc] initWithFrame: CGRectMake(xPosition, 0, dimension*numberIndexes, dimension)];
(I know, not very original)
I would like to know if there is a way to initialize the view programmatically but to say to auto-layout that it has no constraints on the position because right now if the screen turns in landscape mode there is a conflict as the scrollview's frame changes so the distance between the center of the scroll view (on which I set a constraint) and the position of the subview's frame (xPosition) is no longer the same.
As you can see, the view is no longer at the center of the scroll view and I have some constraints broken.
Will attempt to recover by breaking constraint
NSLayoutConstraint:0x7bed6c50 UIView:0x7bed6ad0.centerX == UIScrollView:0x7e273200.centerX
Thanks for your help.
Ok, I found what I was looking for by reading a book about Audio-Layout.
My problem was that audio layout would create constraints behind my back automatically. When using AutoLayout a type of constraints is created from non-autoLayout specifications (The used to describe interface when auto layout didn't exist). So constraints are created using the initial frame of the view. The only thing I had to do was :
[indexesView setTranslatesAutoresizingMaskIntoConstraints:NO];
to disable this creation of constraints from the frame, and then recreate explicitly the constraints for width and height if needed (which wasn't the case for me, but I still made the test) like so :
`NSLayoutConstraint * widthConstraint = [NSLayoutConstraint constraintWithItem:indexesView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0 constant:widthValue];
NSLayoutConstraint * heightConstraint = [NSLayoutConstraint constraintWithItem:indexesView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0 constant:heightValue];
[indexesView addConstraint: heightConstraint];
[indexesView addConstraint: widthConstraint];`
When adding constraints programmatically, don't forget to call : [indexesView setNeedsUpdateConstraints]; so the constraints are recalculated only when needed.
Last info that I read and can be useful in general, when adding a lot of constraints, the apple doc specifies that it is more efficient to use the method :
[myView addConstraints:(NSArray<NSLayoutConstraints *> *)] than to call addConstraint: for each constraint.
Hope it can be useful to someone.

iOS - changing constraint relation programmatically

given the following constraint in ios programmatically:
IBOutlet NSLayoutConstraint *myConstraint;
this constraint is linked in interfacebuilder to the following details:
How do I change the relation attribute programmatically. I tried to look up for a method called setRelation but I don't see it.
According to the documentation, relation is read-only.
What you will need to do, I suspect, is to set
self.myConstraint.active = NO;
Then make a new NSLayoutConstraint programmatically using:
+ constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:
And in the process copying values you want to keep, and replacing the relation.
Then add it to the view hierarchy where appropriate.
You can do like that :
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.yellowView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.redView
attribute:NSLayoutAttributeWidth
multiplier:0.75
constant:0.0]];

Setting view contraints programmatically

I want to set a view constraint on the bottom of self.view programmatically in viewDidLoad. I want the view to end 80px above the bottom of the screen.
How would I go about doing this?
Looking at the AutoLayout guide, it looks like what you could do is something like this:
NSLayoutConstraint *myNewConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual toItem:self.view.window
attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-80.0];
and once you have that constraint created, you need to install it into the view... using something like:
[self.view addConstraint:myNewConstraint];
Now I've never done this, so the values might be a bit off. But hopefully this sends you down the right path!

iOS - constraints animate in an unexpected way in viewdidload?

I'm practicing auto layout and learning about animating constraints.
My first question is. If I am dynamically adding views it seems cumbersome to dynamically add their constraints to the parent view as well. Is there any clean way to accomplish a flexible layout where views can be added and removed programmatically? Or would this mean I should probably think of a simpler solution for what I'm trying to accomplish?
Second question. I have created two views, and some constraints in code. I am just trying to resize the height constraint on the first view on load so that it will become shorter, and the second view will shift upwards accordingly.
here is some code:
first = [[UIView alloc]initWithFrame:CGRectZero];
[first setBackgroundColor:[UIColor blueColor]];
[first setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:first];
UIView *second = [[UIView alloc]initWithFrame:CGRectZero];
[second setBackgroundColor:[UIColor redColor]];
[second setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:second];
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:first attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:20];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:first attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:-20];
top = [NSLayoutConstraint constraintWithItem:first attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:40];
height = [NSLayoutConstraint constraintWithItem:first attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:80];
[self.view addConstraints:#[leading,trailing,top,height]];
[height setConstant:10];
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];
okay so at the bottom here I run my animation.. my second view is already positioned where it would be at the end of the animation. the first view expands from its top left corner, to its bottom right corner. it animates diagonally and ends up with a height of 10.
Can anyone explain this behavior. I noticed if I assign the constraints, and make them animate on an IBAction (button touch) then it will animate as expected.
Second question first. Why are you animating changing in viewDidLoad? At this point we know the view is loaded, but it probably isn't laid out and definitely not going to be visible to the user; consider the constraint layout changes in the viewDidLayoutSubviews method.
As for the first question. David H's answer is one way... and a perfectly fine way. To give a different option, I use constraintsWithVisualFormat:options:metrics:views: which allows me to specify views, then creates all the necessary constraints across all the views. It can be a much simpler way to create constraints across several views. Depending on exactly what you are doing one way might better suit your needs.
Edit based on comment...
With something where you'll have to break constraints, you'll still have to find and break the constraints before creating the new ones. There is no way around that. You'll either have a reference to the constraint you want to break or have to iterate through all the constraints on an object to find it. A B C goes to A B and C where the constraint between B and C is gone. Using the visual format to put in X might be something like #"[B]-20-[X]-20-[C]" which will create a constraint for a 20 point spacing between B and X and a second constraint which will be a 20 point spacing between X and C. As a note, the visual format above specifies horizontal positioning/spacing only. You would need a second line to specify the vertical constraints.
I am doing something similar to this, and the technique can be extended. For each view (really, any object), create a mutable dictionary with a "view" and a "constraints" property. the view is just the view, the constraints are an array of dictionaries containing two objects, a "view" property, and a "constraint" property.
When you decide to add or pull a view, then find the dictionary with the appropriate view property, then interate over the constraints array, and add/remove the constraint (of type NSLayerConstraint) to the sister "view" property in the dictionary.
In this manner you can in one method add and remove all the proper constraints regardless of what they view they affect.
Obviously you need to only have constraints that reference views still in the primary view. However, another way to deal with that is to set the width/height of a view to 0, its still there but is not visible. Or change its alpha to 0.

Resources