NSZombie home

Constraint Based Layout for iOS

Constraint based layout means just what it sounds like. Providing a list of constraints to how different views should be arranged, and then letting the system solve it. This sounds similar in practice to the ‘springs and struts’ system that any iOS developer should already be familiar with, with at least one major difference. You can apply constraints to sibling views, not only the superview. This small difference opens up a entirely new world of what is possible in setting up the layout of views. The API I have put together is heavily based on CAConstraintLayoutManager and should be very quick to jump into if you have used that class before.

The code for the layout system can be found here

Using JWConstraintLayoutView

Here is a simple example of laying out two views, and applying some constraints to them.

//This is just here to provide us a reference so the rest of this example makes sense
JWConstraintLayoutView *layoutView = nil;
layoutView = [[JWConstraintLayoutView alloc] initWithFrame:CGRectMake(0,0,320,480)];

UIView *viewA = [[UIView alloc] initWithFrame:CGRectZero];

viewA.frame = CGRectMake(0.0,0.0,100.0,25.0);
viewA.backgroundColor = [UIColor redColor];
[layoutView addConstraint:[JWConstraint constraintWithView:viewA
                                                 attribute:kJWConstraintMidY
                                                relativeTo:nil
                                                 attribute:kJWConstraintMidY]];

[layoutView addConstraint:[JWConstraint constraintWithView:viewA
                                                 attribute:kJWConstraintMidX
                                                relativeTo:nil
                                                 attribute:kJWConstraintMidX]];

[layoutView addSubview:viewA];

UIView *viewB = [[UIView alloc] init];
viewB.backgroundColor = [UIColor greenColor];
[layoutView addConstraint:[JWConstraint constraintWithView:viewB
                                                 attribute:kJWConstraintWidth
                                                relativeTo:viewA
                                                 attribute:kJWConstraintWidth]];

[layoutView addConstraint:[JWConstraint constraintWithView:viewB
                                                 attribute:kJWConstraintMidX
                                                relativeTo:viewA
                                                 attribute:kJWConstraintMidX]];


[layoutView addConstraint:[JWConstraint constraintWithView:viewB
                                                 attribute:kJWConstraintMinY
                                                relativeTo:viewA
                                                 attribute:kJWConstraintMaxY
                                                    offset:10.0]];

[layoutView addConstraint:[JWConstraint constraintWithView:viewB
                                                 attribute:kJWConstraintMaxY
                                                relativeTo:nil
                                                 attribute:kJWConstraintMaxY
                                                    offset:-10.0]];

[layoutView addSubview:viewB];

After layout you will see something similar to this

Constraints

This example is a direct translation of Apple’s CAConstraintLayoutManger example. Other than the obvious API differences, the only real change is modifying which constraints are set on viewB, as CoreAnimation and UIKit have flipped coordinate spaces.

There are three classes related to the constraints system.

  1. JWConstraintGraphNode
    This is a internal class used by the constraint solving system. You should never have to create one of these manually, or really care about them. I only bring it up here so that you are aware of its existence.
  2. JWConstraint
    JWConstraint allows you to describe your constraints, here is its designated initializer.
    - (id)initWithView:(UIView*)aView
             attribute:(JWConstraintAttribute)anAttribute 
            relativeTo:(UIView*)aRelativeView 
             attribute:(JWConstraintAttribute)aRelativeAttribute 
                 scale:(CGFloat)aScale 
                offset:(CGFloat)aOffset;
    
    • aView is the view on which you are applying the constraint.
    • anAttribute is the attribute on the view which will be calculated. You can only constrain up to two attributes per axis, per view. Meaning only MinX and Width, or MaxX and MidX, but MinX, MidX and Width all together.
    • aRelativeView is the view which you want to base the calculation on. A value of nil here means that the calculation is based on the superview.
    • aRelativeAttribute is the attribute to base the previous attribute on. Any attribute here is valid, even if it is not on the same axis as the input attribute.
    • aScale and aOffset are applied to the result as result = (result * scale) + offset. They default to 1.0 and 0 respectively when using initializers that don’t expose them.
  3. JWConstraintLayoutView
    JWConstraintLayoutView is the workhorse of the three. Internally it will manage all the constraints and perform the actual constraints calculations. Recalculation of the constraints graph, the most expensive part of this, only occurs when new constraints are added. Other than that you should treat this as any other view, and expect it to relayout its subviews based on its constraints. Any unconstrained view will be laid out according to its standard behavior using its autoresizeMask. The layout view should be placed as the superview to any views that you want it to manage, contrary to how CAConstraintLayoutManager works.

A compilable and much more complicated example can be found here.

blog comments powered by Disqus