I have two labels. I want to be able to move both if one is moved. How do I "attach" them together with NSLayoutConstraints? I can do this in IB, but need to do it in code.
also, what are NSLayoutAttributeBaseline, NSLayoutAttributeLeading, and NSLayoutAttributeTrailing?
EDIT:
centering poweredByLabel (aka label02):
[constraints addObject:[NSLayoutConstraint constraintWithItem:poweredByLabel
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:myImage
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0]];
stack the labels and switch vertically:
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[rememberPasswordSwitch]-10-[rememberPasswordLabel]-10-[versionLabel]-[poweredByLabel]-|"
options:NSLayoutFormatAlignAllBaseline
metrics:nil
views:viewsDictionary]];
which produces the error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unable to parse constraint
format: Options mask required views to be aligned on a vertical edge,
which is not allowed for layout that is also vertical.
V:[rememberPasswordSwitch]-10-[rememberPasswordLabel]-10-[versionLabel]-[poweredByLabel]-|...........................................................................................................^'
w/out the NSLayoutFormatAlignAllBaseline option, it runs fine (they stack but are not all centered horizontally).
If you need to do this in code, first create the NSLayoutConstraint(s), then add the constraint(s) to the labels' superview.
There are two ways to create constraints in code. constraintsWithVisualFormat is usually much more concise than constraintWithItem.
// Make label1's NSLayoutAttributeTrailing be the 'standard Aqua space' away from label2's NSLayoutAttributeLeading. Also, vertically align their baselines.
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[label1]-[label2]" options:NSLayoutFormatAlignAllBaseline metrics:nil views:NSDictionaryOfVariableBindings(label1, label2) ] ;
Then you add the constraint(s) to the labels' superview:
[label1.superview addConstraints:constraints] ; // Use `label1.superview` or your own reference to the label's superview.
The Cocoa Auto Layout Guide is short and easy to follow. Give it a read, and I'd be happy to answer any questions you still have.
Edit 1
The option NSLayoutFormatAlignAllBaseline creates constraints (in addition to those created by the VisualFormat string) that vertically align the baselines of all specified objects. If your VisualFormat string is creating vertical constraints (it starts with "V:"), you don't want to use this option. You'd want to use 0 (which means no options), or an option that creates horizontal constraints, like NSLayoutFormatAlignAllCenterX.
Related
I am having an UIImageView (say imageView1) and a UITextView(say textView1) which have to be displayed vertically (one [imageView1] below the other [textView1]) beginning with the same margin position as of textView1. I have to achieve this through autolayout programmatically.
I know that this can be done by setting the vertical constraints like below for both the views.
NSLayoutConstraint constraintsWithVisualFormat:#"V:|[textView1]"
But the problem I have here is I already have many text views(textView2, textView3) arranged in horizontal before and after this textView1.
I have already added many autolayout constraints to this textView1 through storyboard. Based on the different screen size and orientation the textView1 margin differs as per the constraints that are provided on the storyboard for this.
Now how can I provide the autolayout constraint programmatically in such a way that my imageView1 is to align in par vertically with the same margin as that of textView1?
p.s: imageView1 is created programmatically in code but where as all other views that I mentioned above are created through storyboard.
+ Adding images for easy understanding
In the image, imageView1 is the UI Image. I have created it in storyboard just for understanding purpose but in real it will be created programmatically and this have to be aligned to the margin of UITextView (textView1) present below it.
This is the constraint that I want to create it through programmatically(In case this is the real question here :).
This constraint is to always make sure that imageView1 and textView1 start originating from the same margin.
How to define this constraint programmatically ?
Rather than using the visual format, you can just instantiate a constraint directly, e.g.
[NSLayoutConstraint
constraintWithItem:imageView1 attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:textView1 attribute:NSLayoutAttributeLeading
multiplier:1.0 constant:0.0]
Use Masonry for setting constraints programmatically.It is very easy to use and reduce lots of complexities for the user.
https://github.com/SnapKit/Masonry
...you can try to build an UIView, set the constraints that it will need, and use it as a placeholder for your UIImageViews.(later you can add them inside of such a view) or, by the otherside, using an UICollectionView instead.
You can set the options argument in
constraintsWithVisualFormat:options:metrics:views: check Apple Class Reference.
Your code might be as follows
NSString* leadingConstraintsExpression = #"V:[imageView1][textView1]";
NSDictionary* viewsDictionary = NSDictionaryOfVariableBindings(imageView1,textView1);
NSArray* leadingConstraints = [NSLayoutConstraint
constraintsWithVisualFormat:leadingConstraintsExpression
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:viewsDictionary];
[self.view addConstraints:leadingConstraints];
I'm creating a custom UITableViewCell, but I don't think it makes a difference - my question stands alone.
I'm subclassing one of the existing cell styles, and adding one view. The superclass has a view (a UILabel) that adjusts its vertical positioning within the cell depending on whether or not there is content in another label. If there is content in the second label, the first label is vertically centered between the second label and the top of the cell view.
If the second label doesn't have any content, the first label is vertically centered between the top and the bottom of the cell view.
I like this behavior. I'm trying to add a third label that is horizontally next to the first label. I've used autolayout constraints to pin the new label to be near the first label, like this:
[NSLayoutConstraint constraintsWithVisualFormat:#"[firstLabel]-(5)-[thirdLabel]" options:0 metrics:nil views:views];
This is working fine, but I can't figure out how to do something similar with the vertical position. Ideally, I would "pin" the third label to always be at the same vertical alignment as the first label, no matter what that is, but I don't see how to express that in the visual format language.
As an alternative, figuring out how to replicate the behavior of the first label (adjusting it's vertical alignment based on the presence--or lack of--the second label).
This constraint works to correctly vertically align the new label when the second label is present, but it doesn't do anything if the second label is empty on that particular cell:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:[thirdLabel][secondLabel]" options:0 metrics:nil views:views];
How can I express this layout constraint?
Visual Format Language is best used to create constraints aligning items one after the other.
For more complex layouts like the vertical center alignment you are trying to build, you should use NSLayoutConstraint's constraintWithItem:[...]. method.
Here is what you could do:
[NSLayoutConstraint constraintWithItem:thirdLabel
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:firstLabel
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0];
Hope this helps,
I’m beginning to implement AutoLayout, and I’m trying to piece together how I can hook up one of my custom UITableViewCell classes properly. It usually has two UILabel objects, one on top of another, much like the standard subtitle class. I’d like these two labels to be centred in the cell, regardless of the cell’s height, with a given padding between the two.
I assume I can do this by doing something like add the heights of the two labels, add the padding, then subtract that from the height of the cell, and divide by two. However, I’m curious semantically whether this is correct, since I’d be constraining them from the top and bottom of the cell, rather than from each other. Am I missing a trick here?
Secondly, there’s sometimes a third label stacked in there too, so three on top of one another. In that instance, I’d need two sets of the padding, etc., but the question becomes even more relevant: shouldn’t I be constraining them to each other, rather than to the top and bottom of the cell?
So, question is more of a semantic one: if I want to constrain multiple elements vertically inside a parent view, is there a smarter way to do this than the method I suggested above?
(I’m currently implementing AutoLayout entirely in code (using Masonry) since this cell in question has no XIB and isn’t in a Storyboard).
The correct approach is to use a container view, which derives its height from its subviews. The container view is then pinned to the centre of the cell - you wouldn't have any constraints linking the container to the top and bottom edges of the cell.
Within the container, the vertical constraints would be |[label1]-[label2]|, which would make the container the height of the two labels plus the space, and the centre of the container view would be between the two labels.
If you added three labels, it would be |[label1]-[label2]-[label3]| and the centre of the container would be in the centre of the middle label.
In each case the centre of the container would be at the centre of the cell, and you don't need to calculate anything.
Using Interface Builder, I'm able to vertically center multiple UIViews using the following steps. My use case is that I want these multiple UIViews to behave like a group as much as possible but I don't want to introduce any further views in order to achieve that.
Firstly, I select what I consider to be my default Form Factor using the button along the bottom (Form Factor being 3.5 inch or 4 inch retina). Then, I position the buttons in the center of the view by dragging one at a time, and I use the auto-snapping with blue guidelines to help with this.
Once the buttons are in place, I select them one at a time to apply their constraints. Click on a button, then select the Align Constraints menu from the floating buttons at the bottom right in Interface Builder (I see a set of 4, and the Align is the one on the left, next to the Form Factor button).
In this menu, check the Vertical Center In Container checkbox, then click to open the dropdown menu next to the value, which is probably 0. From that list, choose Use Current Canvas Value. Then, hit the Add Constraints button (possibly labelled Add 1 Constraint).
As you do this, you'll probably know it's working if you see the values next to Vertical Center In Container becoming set to Y-offset values you might guess are correct by glancing back to your arrangement of UIViews.
Apologies if you don't want to use Interface Builder, but I had trouble achieving this myself and wanted to mention these steps. You could always use IB as a one-off, and programmatically log out the constraints in a test, then take them and apply in your code-only solution.
Assuming you have label1, label2, label3, and cell
// Center the labels
[NSLayoutConstraint constraintWithItem:label1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual
toItem:cell.contentView attribute:NSLayoutAttributeCenterX
multiplier:1.0 constant:0]
[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual
toItem:cell.contentView attribute:NSLayoutAttributeCenterX
multiplier:1.0 constant:0]
[NSLayoutConstraint constraintWithItem:label3 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual
toItem:cell.contentView attribute:NSLayoutAttributeCenterX
multiplier:1.0 constant:0]
// Vertical alignment/spacing of 8 between each label
[NSLayoutConstraint constraintWithItem:label1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
toItem:cell.contentView attribute:NSLayoutAttributeTop
multiplier:1.0 constant:0]
[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
toItem:label1 attribute:NSLayoutAttributeBottom
multiplier:1.0 constant:8]
[NSLayoutConstraint constraintWithItem:label3 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
toItem:label2 attribute:NSLayoutAttributeBottom
multiplier:1.0 constant:8]
You can nest everything inside a for() loop I guess so you won't have to repeat the constraints
No code. Just edit in the storyboard.
I just read few information about how to manipulates constraints number programatically, but I think I need to see it visually using Xcode. So I decided to put a UIView like this and see what's going on with Constraints :
automatically it will create 4 constraints :
after modifying the number, I got what's the meaning of all this. there we have 2 vertical space and 2 horizontal space. but let's focus on vertical first...
one of vertical space measured from top of the screen (superview, I guess), I give this number 30. and other vertical space measured from the bottom, I give -50.
now, when it turns into code, I really don't understand how to give this 2 numbers. as far as I know, here's how to set constraint with code :
NSLayoutConstraint *myConstraint =[NSLayoutConstraint
constraintWithItem:_screenView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:_container
attribute:NSLayoutAttributeHeight
multiplier:20
constant:200];
[_screenView addConstraint: myConstraint];
let's assume _screenView is a superview and _container is UIView placed on top of it. I have 3 questions :
How to set both vertical space (30 and -50) manually with code? because from storyboard I don't see any different name or ID of vertical space. they both have same name. do I have to create 2 NSLayoutConstraint ?
Then how to add it into _screenView? do I have to create this : [_screenView addConstraint: myConstraint]; twice?
what multiplier used for? because I don't see that option in storyboard.
thank you very much.
I think it's important to think of this formula from Apple's Auto Layout Guide when working with constraints:
y = m*x + b, where:
y and x are attributes of views.
m and b are floating point values.
You can think the constraint for the top of _container like this:
_containter.top = m*_screenView.top + b
When you are creating a constraint using NSLayoutConstraint constraintWithItem:... the multiplier argument is "m" and constant is "b" in this formula. To create a constraint that keeps the top of _container 30 points below the top of _screenView you would do this:
NSLayoutConstraint *myConstraint =[NSLayoutConstraint
constraintWithItem:_container
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_screenView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:30.0];
The bottom constraint would be:
NSLayoutConstraint *myConstraint =[NSLayoutConstraint
constraintWithItem:_container
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:_screenView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-50.0];
Also keep in mind that in the UIKit coordinate system X values go up from left to right and Y values go up from top to bottom. In other words, 0,0 is at the top-left of screen and the values go up from there.
If you want to create the four contraints you show above you will have to create them all individually. You could also use the constraintsWithVisualFormat:options:metrics:views: method of NSLayoutConstraint to make multiple at once. I find it more clear to write them out the way I did above.
I'd like to map dots along a line.
The line length depends on device and orientation and is always stretched across the whole screen. Therfore it would make most sense to me to position the dots using a relative percentage value.
So far I only found constraints being defined in some sort of point value (Doc)
Is it possible to use percentage values as constraints as well? Any ideas on how to position these dots in a scalable way ... or do I need do this conversion/positioning "manually"?
Yes, you can position the dot as a fraction of the view's width. The NSLayoutConstraint method, constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:, has that multiplier parameter that lets you use a fractional relationship between a superview and its subview. The superview's right edge will be the width of that view (the screen if its a full width view), so if you create a constraint like below, the dot will be positioned at a fractional distance along the line:
-(void)viewDidLoad {
[super viewDidLoad];
[self.view removeConstraint:self.leftConDark];
[self.view removeConstraint:self.leftConLight];
NSLayoutConstraint *lcd = [NSLayoutConstraint constraintWithItem:self.darkButton
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:.5
constant:0];
NSLayoutConstraint *lcl = [NSLayoutConstraint constraintWithItem:self.lightButton
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:.9
constant:0];
[self.view addConstraints:#[lcd,lcl]];
}
In this example I'm positioning two UIButtons (info type dark and light). I added them in IB, and made IBOutlets to their constraints they have to the left side of the view (that's what the system gave me, it could have been to the right side -- it doesn't matter since you just delete them anyway. If you are making the dots in code, you wouldn't need to do this). In code I remove those constraints, then add new ones that will put the center of the buttons at 50% and 90% of the way across the screen.
I'm not an auto-layout expert (or even competent yet!) but as of XCode 6, you seem to be able to do this in IB.
Select the object.
Show the size inspector (click on ruler)
Double-click on the constraint rectangle (not obvious! "Edit" takes you somewhere else)
Here you can set the multiplier just like in the answer above.