can I set constraints directly to superview in a custom view? - ios

A pretty simple question I reckon:
one UIViewController
one custom UIView
The controller only does:
-(void)loadView{
[super loadView];
self.sideMenu = [[sideMenuView alloc]init];
[self.view addSubview:self.sideMenu];
}
and in the UIView I would like to do something like:
self.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeLeading multiplier:1 constant:100];
[self.superview addConstraint:constraint];
So that when I create the UIView in the controller its constraints is already set in relation to the controller.
I have tried and nothing crashes but the UIView gets realy weird x and y coords
Maby I need to update the constraints? Or maby this isnt at all possible?

I'm not sure what ui behavior you are exactly looking for since it appears that you are trying to tie the leading space of your view to the leading space of it's superview. Being the leading space, the space on the left of the view, could it be that you are looking for the more common "stick my left side 100 pixels from my parents left border"? Anyway, in either case, I would connect an outlet from the controller to the custom view (i.e. myCustomView below) and then build the constraint in the UIViewController and not the UIView by overriding:
- (void)updateViewConstraints {
[super updateViewConstraints];
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:myCustomView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:myCustomView.superview
attribute:NSLayoutAttributeLeading
multiplier:1
constant:100];
[myCustomView addConstraint:constraint];
}
Apple has an interesting page with a table showing the various runtime entry points for autolayout at this address:
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/Articles/runtime.html#//apple_ref/doc/uid/TP40010853-CH6-SW1
You might consider adding other constraints as well. Auto layout has the the tendency to exploit any freedom you leave unchecked in the worst possible way ;-)

So leading edge is not enough.
You need enough constraints to satisfy vertical and horizontal layout.
In one direction you need at least
one edge & width (or hight)
Or
Two edges ( implicit width or height )
Or
A horizontal (or vertical) center based constraint and an explicit width ( or height respectively)
The thing about width and height is that they can also be determined by intrinsic content size.
Add constraints after adding the view to the superview.

A bit late but PureLayout is pretty handy https://github.com/smileyborg/PureLayout

Related

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.

How to stick UIView to the right side of it's superview programmatically

I am desperately trying to stick one of my UILabels to the right edge of it's superview while the label's width is variable (it's a time so the thing is getting bigger and should be expanding to the left, this is done using sizeToFit inside of the label when text is set).
So far I have tried loads of things but closest I got with:
_elapsedTimeRightConstraint = [NSLayoutConstraint constraintWithItem:_elapsedTimeView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1.0 constant:-150];
While the label is initially set to 150px width. But when I modify the constant, it all goes to hell.
_elapsedTimeRightConstraint.constant = (_elapsedTimeView.frame.size.width * -1);
[self layoutIfNeeded];
So my question is, how do I align trailing edges of a view and it's superview (so it sticks to the right) when the width of the subview is constantly changing. I have been using FLKAutoLayout elsewhere in the project so if this can be done this framework easily than great, but basic autolayout solution would be amazing too!!!
First, make sure that translatesAutoresizingMaskIntoConstraints is set to NO, if you are creating the label programmatically.
The first constraint you need is "label.trailing = superview.trailing".
[NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTrailing
multiplier:1.f
constant:0.f]
This will pin the right edge (on left-to-right languages) of the label on the right edge of the superview.
You will now need a constraint for the Y position.
In my test, I have vertically centred the label with the following constraint:
[NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeCenterY
multiplier:1.f
constant:0.f]
Now comes the trick!
Every time you change the text on the label, you need to recalculate the frames with AutoLayout.
[superview setNeedsLayout];
[superview layoutIfNeeded];
AutoLayout will:
1) Ask the label of its new size (based on its text).
2) Adjust the size of the label.
3) Pin the trailing edge of the label to the trailing edge of the superview.
Further research
The issue with UILabel is that when you're using AutoLayout and you set text, its intrinsicContentSize changes, but it doesn't trigger a layout update.
A way to enforce this without subclassing UILabel is to use Objective-C runtime.
#interface UILabel (AutoLayout)
- (void)swz_setText:(NSString*)text;
#end
#implementation UILabel (AutoLayout)
+ (void)load
{
NSLog(#"Swizzling [UILabel setFont:]...");
Method oldMethod = class_getInstanceMethod(self, #selector(setText:));
Method newMethod = class_getInstanceMethod(self, #selector(swz_setText:));
method_exchangeImplementations(oldMethod, newMethod);
}
- (void)swz_setText:(NSString*)text
{
if (![text isEqualToString:self.text]) {
[self setNeedsLayout];
}
[self swz_setText:text]; //This now points to "setText:" - not a mistake!
}
#end
In this category, I'm "enhancing" setText: implementation by calling setNeedsLayout if the text changes.
Now you just need to invoke layoutIfNeeded on the superview to recalculate/realign the label frame.
Click here for the playground (Swift 2.0 - Xcode 7) where I've tested my code.
I hope this helps.
Hello here are some points to achieve what you want:
Set NSLayoutConstraintTrailing constant equal to 0 and the leading constraint NSLayoutAttributeLeading set it as greater than or equal the value you want.
Use NSLayoutConstraintTrailing and NSLayoutAttributeLeading instead of right and left to handle other languages
I hope this helps
[yourLabel sizeToFit];
CGRect frame = yourLabel.frame;
frame.x = parentView.frame.size.width - yourLabel.frame.size.width;
yourLabel.frame = frame;
It completely ignores the "best practice" of using the autolayout features, but if you just can't stand it anymore....that should work. ;-)

UITextView stretches horizontally but not vertically inside UIScrollView

My task is very simple - just display vertically scrolling text inside custom UIView.
I have an UIScrollView with some container with one UITextView in it. Container is needed to add more items later. I use IB(xib file) to add autolayout constraints like shown below:
But everything I see is:
And it's scrolling horizontally :-(
I've tried setting contentSize of UIScrollView:
At initWithCoder, didMoveToSuperview, willMoveToSuperview = no effect
At custom method, called from main viewcontroller = no scrolling at all, and I see the same picture.
Thanks a lot!
Edit: Text View's and Container View's intristic size are set to "placeholder". I understand that I should limit width of ContainerView, but it should work with all screen sizes and orientations, so setting width in code, IMO, worse than setting constraints.
Yeah! I've found the solution!
If someone facing the same problem:
You should add left and right constraints not to UIScrollView, but to superview of UIScrollView (to super-super-view).
Unfortunately, this cannot be done in IB, but you can do it within code:
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual
toItem:self.containerView attribute:NSLayoutAttributeLeft
multiplier:1.0 constant:-30]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual
toItem:self.containerView attribute:NSLayoutAttributeRight
multiplier:1.0 constant:30]];
Where self.containerView is view inside UIScrolllView and self.view is view outside UIScrollView
So:
You set vertical constraints to UIScrollView and horizontal constraints to top-level view.

Set a UIView width twice another view using autolayout

In my screen I have two view that are horizontally near to each other. I want the width of first view be twice of the width of second view.
I man for example, if right view has width=200 the second one show by with=100.
As I search and look in auto-layout, it has options for alignments and spaces between views. Do it has option for defining such relationships too?
You can do this programmatically by adding manual constraints that work with autolayout. I'm sure using InterfaceBuilder is also an option.
UIView *firstView;
UIView *secondView;
[firstView addConstraint:[NSLayoutConstraint constraintWithItem:secondView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:firstView
attribute:NSLayoutAttributeWidth
multiplier:2.0
constant:0]];
Note the multiplier there is 2.0 which is where it forces the width to be double.

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