Creating individual layout constraints
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 >=
or <=
, and in some cases, multiplier
and 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.
view1
The view that you want to be affected by the constraint.
attr1
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 NSLayoutAttribute
enum.
relation
The relationship between the attribute and the right hand side of the constraint equation. Either NSLayoutRelationEqual
, NSLayoutRelationLessThanOrEqual
or NSLayoutRelationGreaterThanOrEqual
.
view2
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.
attr2
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.
multiplier
The value by which attr2
should be multiplied to give the value for attr1
.
constant
The value to add to attr2
(after the multiplier has been used) to give the value for attr1
.
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
[view constrainToSize:CGSizeMake(200.0,100.0)];
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
[view centerInContainerOnAxis:NSLayoutAttributeCenterX];
or
[view centerInView: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:
Advantages:
- 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)
Disadvantages:
- More verbose code (
addConstraint:
andaddConstraints:
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.
The category code is available on GitHub, and you can also get hold of it via CocoaPods.