Compiled Chronicles

A software development blog by Angelo Villegas

iOS: NSLayoutConstraint

With the release of Xcode 5 and iOS 7 the need to learn, understand, and use auto layout increased. Auto layout were introduced in iOS 6.0; with auto layout, it is easier to design the interface for multiple screen sizes and also for multiple languages (e.g., Arabic, Japanese, Chinese, etc…). It’s not hard to design a user interface for a screen that is always guaranteed to be the same size and bounds, but if the screen’s frame can change, the positions and sizes of your UI elements also have to adapt to fit into these new dimensions.

What is Auto Layout

Auto Layout is a constraint-based, descriptive layout system available in iOS 6.0 and above. With auto layout, you describe the relationship between views and iOS calculates the location and size of the views at runtime.

Auto Layout is the only way that views are laid out in iOS, it always translates them to constraints at runtime.

Auto Layout is a system that lets you lay out your app’s user interface by creating a mathematical description of the relationships between the elements. You define these relationships in terms of constraints either on individual elements, or between sets of elements. Using Auto Layout, you can create a dynamic and versatile interface that responds appropriately to changes in screen size, device orientation, and localization.

Constraints with Code

Views that are aware of Auto Layout can coexist in a window with views that are not. That is, an existing project can incrementally adopt Auto Layout — you don’t have to make it work in your entire app all at once. Instead, you can transition your app to use Auto Layout one view at a time using the property translatesAutoresizingMaskIntoConstraints

Even though Interface Builder provides a very convenient interface for working with Auto Layout, you can also add constraints programmatically. If you add or remove views at runtime, for example, you’ll need to add constraints programmatically to ensure that your interface responds correctly to changes in size or orientation. Adopting and transitioning your app to use Auto Layout using Interface Builder will still need some work and if you want to start using code, it is suggested to start by setting translatesAutoresizingMaskIntoConstraints‘s property to NO, this way the auto resizing mask of a view will not be translated into constraints.

view.translatesAutoresizingMaskIntoConstraints = NO;

Constraint with Item

Creating individual constraint is slower and needs more lines of code, but you can control what kind of constraint you want to add. In order to create a constraint, an instance of NSLayoutConstraint must be created and initialised with the appropriate settings. Creating a single constraint is done using this method:

[NSLayoutConstraint constraintWithItem:(id)view1
                             attribute:(NSLayoutAttribute)attr1
                             relatedBy:(NSLayoutRelation)relation
                                toItem:(id)view2
                             attribute:(NSLayoutAttribute)attr2
                            multiplier:(CGFloat)multiplier
                              constant:(CGFloat)constant]

Constraints are of the form: view1.attr1 = view2.attr2 * multiplier + constant If your equation does not have a second view and attribute, use nil and NSLayoutAttributeNotAnAttribute. This method prefers completeness of expressibility and is most useful for fixed ratios such as: label.width = 2 * label.height. 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.

Parameters
view1The view that you want to be affected by the constraint.
attr1The 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.
relationThe relationship between the attribute and the right hand side of the constraint equation. Either NSLayoutRelationEqualNSLayoutRelationLessThanOrEqual or NSLayoutRelationGreaterThanOrEqual.
view2The 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.
attr2The 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.
multiplierThe value by which attr2 should be multiplied to give the value for attr1.
constantThe value to add to attr2 (after the multiplier has been used) to give the value for attr1.

NSLayoutRelation & NSLayoutAttribute

typedef NS_ENUM(NSInteger, NSLayoutRelation) {
    NSLayoutRelationLessThanOrEqual = -1,
    NSLayoutRelationEqual = 0,
    NSLayoutRelationGreaterThanOrEqual = 1,
};

typedef NS_ENUM(NSInteger, NSLayoutAttribute) {
    NSLayoutAttributeLeft = 1,
    NSLayoutAttributeRight,
    NSLayoutAttributeTop,
    NSLayoutAttributeBottom,
    NSLayoutAttributeLeading,
    NSLayoutAttributeTrailing,
    NSLayoutAttributeWidth,
    NSLayoutAttributeHeight,
    NSLayoutAttributeCenterX,
    NSLayoutAttributeCenterY,
    NSLayoutAttributeBaseline,
    
    NSLayoutAttributeNotAnAttribute = 0
};

Real-World Example

We will use several instances of NSLayoutConstraint to create a 100×100 label that is centred on the view.

UILabel label = [[UILabel alloc] init];
label.backgroundColor = [UIColor lightGrayColor];
label.translatesAutoresizingMaskIntoConstraints = NO;
label.text = @"Label";
label.textAlignment = NSTextAlignmentCenter;
[self.view addSubview: label];

NSLayoutConstraint *constraintWidth = [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:100];
NSLayoutConstraint *constraintHeight = [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:100];
[label addConstraint:constraintWidth];
[label addConstraint:constraintHeight];

NSLayoutConstraint *constraintX = [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0f constant:0];
NSLayoutConstraint *constraintY = [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0];
[self.view addConstraint:constraintX];
[self.view addConstraint:constraintY];

We created four constraint: two for the label and two for the superview.

What’s the difference? Why not put it all to the superview?

In the case of a constraint that references a single view, the constraint must be added to the immediate view or to the parent of the view. When a constraint references two views, the constraint must be applied to the closest ancestor of the two views. For the case above, it was set to the immediate view. The first two constraints, constraintWidth and constraintHeight, are single-view-referenced constraint that gives the label fixed width and height while the other two, constraintX and constraintY, are two-view-referenced constraint that makes the label centred within the superview.

Extra Example

Make a view half the width of it’s superview

[NSLayoutConstraint constraintWithItem:view
                             attribute:NSLayoutAttributeWidth
                             relatedBy:NSLayoutRelationEqual
                                toItem:superview
                             attribute:NSLayoutAttributeWidth
                            multiplier:0.5
                              constant:0]];

Pin a view to the left edge of the superview

[NSLayoutConstraint constraintWithItem:label
                             attribute:NSLayoutAttributeLeft
                             relatedBy:NSLayoutRelationEqual
                                toItem:self.view
                             attribute:NSLayoutAttributeLeft
                            multiplier:1.0
                              constant:0.0];

Visual Format Language

The visual format language prefers good visualization over completeness of expressibility. It allows the concise building of your layout using an ASCII-art type format string. Although most of the constraints that are useful in real user interfaces can be expressed using the language, some cannot.

[NSLayoutConstraint constraintsWithVisualFormat:(NSString *)format
                                        options:(NSLayoutFormatOptions)options
                                        metrics:(NSDictionary *)metrics
                                          views:(NSDictionary *)views];

This method returns an array of constraints that, combined, express the constraints between the provided views and their parent view as described by the visual format string. The constraints are returned in the same order they were specified in the visual format string.

Parameters
formatThe format specification for the constraints.
optionsOptions describing the attribute and the direction of layout for all objects in the visual format string.
metricsA dictionary of constants that appear in the visual format string. The keys must be the string values used in the visual format string, and the values must be NSNumber objects.
viewsA dictionary of views that appear in the visual format string. The keys must be the string values used in the visual format string, and the values must be the view objects.

Visual Format Syntax

The following are examples of constraints you can specify using the visual format.

Syntax
FormatResult
Complete Line of LayoutH:|[label(100)]-(>=20,<=200)-
[label2(==label)]-(>=20,<=200)-|
label should be 100 points in width with right spacing of not lower by 20 and not higher by 200 points with label2 while label2 should be equal width to label and have a right spacing of not lower by 20 and not higher by 200 points.

Real-World Example

UILabel *label = [[UILabel alloc] init];
label.backgroundColor = [UIColor lightGrayColor];
label.translatesAutoresizingMaskIntoConstraints = NO;
label.text = @"Label";
label.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:label];

UILabel *label2 = [[UILabel alloc] init];
label2.backgroundColor = [UIColor lightGrayColor];
label2.translatesAutoresizingMaskIntoConstraints = NO;
label2.text = @"Label2";
label2.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:label2];

NSLayoutConstraint *constraintHeight = [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:100];
[label addConstraint:constraintHeight];

NSLayoutConstraint *constraintHeight2 = [NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:100];
[label2 addConstraint:constraintHeight2];

NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[label(100)]-(>=20,<=greater)-[label2(==label)]-(>=20,<=greater@1000)-|" options:0 metrics:@{ @"greater" : @200 } views:@{ @"label" : label , @"label2" : label2 }];
[self.view addConstraints:constraints];

NSLayoutConstraint *constraintY = [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0];
[self.view addConstraint:constraintY];

NSLayoutConstraint *constraintY2 = [NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0];
[self.view addConstraint:constraintY2];

Summary

All in all, the visual format language is an interesting way to describe the layout of an interface, and will certainly make laying out interfaces by hand easier. Auto Layout is a great way to write flexible and maintainable views dependencies. In some cases it’s better to use the visual format, but when it’s not enough you can go straight to write beautiful constraints using individual constraint initialised by creating constraints explicitly.

Comments

  1. Allison Avatar

    Where should this code go? I tried putting it in the viewDidLoad method, but got a fatal error. I have already declared the label in the interface, so maybe that’s the issue? But if so, how do I fix it?
    Thanks!

    1. If you already declared a label in the interface, you can connect an outlet.

Leave a Reply

Your email address will not be published. Required fields are marked *