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 | |
---|---|
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 . |
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 | |
---|---|
format | The format specification for the constraints. |
options | Options describing the attribute and the direction of layout for all objects in the visual format string. |
metrics | A 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. |
views | A 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 | ||
---|---|---|
Format | Result | |
Complete Line of Layout | H:|[label(100)]-(>=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.
Leave a Reply