Other posts in the Autolayout series:
In the previous two posts in this series we’ve talked about creating constraints in interface builder and in code using the visual format language. In this final part I’m going to discuss creating individual constraints, and also some category methods on
UIView that can make creating layouts in code a lot simpler, and to make your layout code much more readable.
Creating a single constraint is done using this method:
+ (id)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c
Even writing the definition takes several lines, so you can see why this method is not recommended as a way to build your entire layout. However in some cases this method is the only way to build the layout you need, particularly since it exposes the multiplier and constant properties, which are not readily available for the other methods of constraint creation.
First, let’s have the usual discussion of each argument in the main method and try to explain what it is all for. Remember that constraints are usually expressed as:
attribute1 == multiplier x attribute2 + constant
== can also be
<=, and in some cases,
attribute2 are irrelevant, so we are just looking at:
attribute1 == constant
With that in mind, let’s go through the method. It returns a single instance of
NSLayoutConstraint, rather than the array of constraints returned by the visual format language method.
The view that you want to be affected by the constraint.
The attribute of
view1 that you want to be controlled by the constraint, such as the left edge, the center X position, the width and so forth. This is a single value from the
The relationship between the attribute and the right hand side of the constraint equation. Either
The view that has the attribute that you want to use to define the right hand side of the constraint equation. If you are just setting a value to a constant, use
nil for this argument.
The attribute of
view2 that you want to use to define the right hand side of the constraint equation. If you are just setting a value to a constant, use
NSLayoutAttributeNotAnAttribute for this argument.
The value by which
attr2 should be multiplied to give the value for
The value to add to
attr2 (after the multiplier has been used) to give the value for
So far, so much rewording of the existing documentation. Let’s look at some concrete examples.
Fix a view to a specific width
[view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:200.0]];
Sets the width of
view to 200.0 points.
Make a view half the width of its superview
[view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeWidth multiplier:0.5 constant:0]];
Pin a view’s edge to the edge of another view
[view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0]];
Centre a view on an axis within its superview
[view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
It’s all pretty simple, but pretty verbose too. All of the invocations look very similar, so when reading back the code it is hard to see the intent without having to parse it manually. We can do better.
##NSLayoutConstraint creation - the missing methods
Fix a view to a specific size
A size of 0.0 in either dimension means that no constraint will be created.
Pin a view to its superview’s edges
Very often you want to pin a view to one or more of the edges of its superview, sometimes with an inset. That’s at least two lines of VFL, or more if you’re creating the constraints individually.
[view pinToSuperviewEdges:JRTViewPinLeftEdge | JRTViewPinRightEdge inset:0.0];
(A new bitmask had to be introduced here to allow combining edges, and to give the option of pinning all edges with a single value)
Pin a view to the edge of another view
[view pinEdge:NSLayoutAttributeLeft toEdge:NSLayoutAttributeRight ofView:view2 inset:0.0];
Centre a view within its superview
A note on the approach
I’ve chosen to use a category on UIView to achieve things here since it gives a more direct expression of intent, and it also leads to more concise code. These methods have all been used by me in production code, and were added as I felt a requirement for them.
A category on
NSLayoutConstraint would be an alternative, with the following advantages and disadvantages:
- More consistent with the existing API
- Gives access to the constraints as they are created (if you wanted to store them in properties for later adjustment or bulk removal / application)
- More verbose code (
addConstraints:all over the place)
- Would need to pass in view references to all the methods, making them even longer
- Not as immediately readable (in my opinion)
The only significant advantage is the second one, but I’ve found that the times I want to store a specific constraint are rare enough that I just use the long-winded method to make it in that case.