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/
Related
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];
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.
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. ;-)
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
I want an iPad layout that that has two panels side by side, to fill the width of the screen and both are as tall as the screen. My attempts have led to as follows
self.view addConstraints:
#"|[_sidePanel(300)]-1.0-[_mainPanel]|"
#"V:|[_sidePanel]|"
#"V:|[_mainPanel]|"
Inside __sidePanel_ I'm trying to create more constraints on child views.
Note the _sidePanel view is a UIScrollView.
I want to stack 2 views on top of one another in the side panel.
So I add the following constraints to__sidePanel_.
_sidePanelView addConstraints:
#"|[_top(300)]|"
#"|[_bottom(300)]|"
#"V:|[_top]-5.0-[_bottom]|"
It seems I need to specify the width for these two views in order to avoid ambiguity.
But I want the bottom view to fill the remaining space of __sidePanel_.
If I just pin __bottom_ to the bottom of __top_ (which gets a defined height at some point based on its contents) and to the bottom of its parent __sidePanel_, the __sidePanel_ and __bottom_ are both ambiguous; which makes sense i guess since the constraints are awfully similar (and which doesn't get avoided by adding the constraint for __bottom_ to the __sidePanel_ view as opposed to the topmost self.view).
If I hardcode a height for __bottom_, i resolve ambiguity but I don't want a defined height; i want it to fill remaining space in __sidePanel_.
Any suggestions on what I could try to resolve ambiguity but still achieve what I'm after?
You need to specify a height for either top or bottom -- it sounds like top gets a defined height at some point, but you need set a defined height for it initially, which you can change later.
Also, there's no need to specify the widths (300) for either top or bottom, since you've pinned them to the sides of sidePanel, which itself has a defined width. so these constraints worked fine with no ambiguity:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[_sidePanel(300)]-1.0-[_mainPanel]|" options:0 metrics:nil views:viewsDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_sidePanel]|" options:0 metrics:nil views:viewsDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_mainPanel]|" options:0 metrics:nil views:viewsDict]];
[_sidePanelView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[_top]|" options:0 metrics:nil views:viewsDict]];
[_sidePanelView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[_bottom]|" options:0 metrics:nil views:viewsDict]];
[_sidePanelView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_top]-5.0-[_bottom]|" options:0 metrics:nil views:viewsDict]];
self.topHeightCon = [NSLayoutConstraint constraintWithItem:self.top attribute:NSLayoutAttributeHeight relatedBy:0 toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:300];
[self.top addConstraint:self.topHeightCon];
Later, when you calculate the actual height for top, you can use self.topHeightCon.constant = (some value) to adjust its height.
In my case it came down to the fact that the view I was trying to have subviews constrain to its bounds was a UIScrollView, which wasn't happening. I since changed it to a UIView and voila my constraints work. And there you have it.