Phil Baudoin

Simple Constraint

May 28, 2014

Auto Layout is an API for iOS (and OS X) that lets you describe your layout as a series of constraints that must be satisfied, instead of calculating the exact position and size of elements yourself (if you want to learn Auto Layout, start here, then read this, and after a bit of practice contorting your mind in strange ways, you’ll be good to go). Constraints can be defined either in code or in interface builder. I generally prefer code, but there are a few annoyances when doing things this way. This post will explain some of the reasoning behind Simple Constraint, a library I made to address those annoyances.

The Problems

If you want to center a view within another view, you might write something like this:

//circle is a subview of square
NSArray *constraints = @[
    [NSLayoutConstraint constraintWithItem:circle
    [NSLayoutConstraint constraintWithItem:circle
circle.translatesAutoresizingMaskIntoConstraints = NO;
[square addConstraints:constraints];

There are three main problems here:

  • The syntax is very verbose, even by Objective-C standards, and not particularly easy to understand at a glance.
  • Since Auto Layout exists alongside the old “AutoresizingMask” system of springs and struts, you must explicitly disable that system on views when you add Auto Layout constraints
  • Constraints must be added to the nearest common ancestor of the two items it references. While this is not particularly difficult to figure out manually, I don’t see any reason not to leave that up to the computer1.

There’s another built-in method for describing constraints that can make things a bit easier: Visual Format Language. It looks something like this:

[parent addConstraints:
    [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(sideMargin)-[thumb1]-10-[thumb2(==thumb1)]-(sideMargin)-|"
    metrics:@{@"sideMargin" : @15}
      views:@{@"thumb1" : thumb1, @"thumb2" : thumb2}]

This is a bit more concise, but it adds problems of its own:

  • It only lets you use some of Auto Layout’s capabilities. The first example in this post, centering an object within another object, requires weird hacks to accomplish with VFL.
  • You have to create dictionaries of views and metrics to pass in (you can save a little typing by using NSDictionaryOfVariableBindings, but that creates its own problems2).
  • It relies on strings that aren’t evaluated until runtime. That means if you make a mistake, you don’t know about it until that part of your code has been executed. You also don’t get syntax highlighting or code completion.

A Solution

A number of Auto Layout libraries have sprung up to address these shortcomings. Each tries to balance readability, conciseness, and capability, while using a standard objective-C syntax. But, the limitations of using a standard syntax lead to tradeoffs, either where conciseness and direct use of variables are prioritized above readability, or strings are used to describe at least some part of the constraint, and we lose the advantages of direct use of variables.

However, when I ignore the limitations of “standard” Objective-C and try to come up with something from scratch, I end up with something concise and readable, while using variables directly, like this:

//view1 and view2 are subviews of container
view1.left = view2.left + leftOffset;
view1.width = view2.width * 1.6 + 8;
view1.height = 100;
view1.right <= container.right;

Since Objective-C doesn’t have operator overloading, there’s no way I can think of to make that syntax work to create Auto Layout constraints in isolation, but with a few shenanigans we can get something pretty close:

    view1.left = view2.left + leftOffset;
    view1.width = view2.width * 1.6 + 8;
    view1.height = 100;
    view1.right = container.right - flex();

You describe a set of constraints within a block. This block is then evaluated several times, allowing the library to differentiate between the constant and the multiplier3. Instead of using >= and <= to describe less-than-or-equal and greater-than-or-equal relationships, you include + flex() or - flex() on the right side of your equation (less concise, but I actually find it slightly easier to understand).

The view on the left side of the equation has translatesAutoresizingMaskIntoConstraints set to NO. This property is not changed for views that are only referenced on the right side of an equation, because there are a cases where you may want to reference a view’s property in a constraint, but still allow it to retain it’s autoresizing behavior4. Constraints are automatically added to the nearest common ancestor.


It Pollutes the Global Namespace with Macros

The macros AddConstraint and AddConstraints are added to the global scope, which pollute things a bit. This is a fairly minor annoyance, but much worse is…

It Pollutes All UIViews with Extra Properties

I consider this the library’s largest shortcoming. Every view has a number of properties added (left, centerX, baseline, etc.) Since these properties are only meaningful inside of an AddConstraint block, it’s annoying and misleading to have the available even outside of those blocks. I considered several ways to solve this problem (like having block parameters that represent the views, so the properties are only accessible inside the block), but couldn’t come up with anything that didn’t hurt readability.

Since the property names are fairly generic, they could conflict with other libraries or future additions to the SDK. This can be addressed by using the prefixed version of the library, but I find the prefixed properties to be less readable and kind of ugly.

The Syntax Implies More Functionality Than There Is

Because of the tricks being used to figure out which constraints to create, the equation for each constraint has to reduce down to the following form: = * multiplier +/- constant +/- flex();

However, since you’re just writing simple equations, it looks like it should also be possible to write equations like these:

view1.width = view2.width = view3.width;
view2.left = parentView.left + view1.width + 10;

Even though this can be avoided fairly easily by someone who reads the documentation and understands the limitations, it’s unfortunate that the syntax doesn’t enforce the correct form.

Wacky Behaviors Are Possible

It’s possible to trip the library up, and write something that would fool it into creating constraints that weren’t really being specified:

//This creates a constraint for view.left = view.right + 1 + flex()
    static CGFloat number = 2.0;
    view.left = number;
    number += 1.0;

I tend to think this isn’t that big of a problem. If your goal is to make code misbehave, you’ll always be able to do it, and I it’d be pretty rare that you’d write something like this accidentally.

Most of these shortcomings are the result of liberties taken in the library’s implementation, in pursuit of a nicer syntax. Since those implementation details can be largely ignored when using the library, I find that to be a worthwhile tradeoff. Take a look at the library on GitHub and see what you think.

  1. If there is a case where determining this automatically isn’t what you would want, I’d love to know, but I haven’t come across one. 

  2. With NSDictionaryOfVariableBindings you have to remember to update your VFL string if you change a variable name, the change won’t be done by most automatic refactoring tools. Also, you can’t use properties (i.e. self.someView), you’re limited to using only ivars. 

  3. Each SimpleConstraint property is a float, and every equation is evaluated 3 times. The first time through, every property getter returns 1, and flex() returns 0. The second time through, each getter returns 2. If you compare the first and second values like points on a line, you can calculate the “slope” to get the multiplier, and the “intercept” to get the constant. The third time through, each property returns the same value as the 2nd time, but now flex() returns a 1. If the value of the 3rd run is higher than the 2nd, that means there as a + flex() in the equation, and the relationship is “greater than or equal”. If the value is lower, it’s “less than or equal”, and if the value is the same, the relationship is “equal”. Every call to a getter is recorded in a global list, and every call to a setter checks that list to determine which view was referenced most recently 

  4. For example setting translatesAutoresizingMaskIntoConstraints to NO on a UIViewController’s view can lead to strange results. 

That's it.