Christian Beer

Christian Beer

Itches With Cocoa Autolayout (With Solution)

Mac OS X Lion (10.7) adds a very capable new method to Cocoa to layout views. Say goodbye to struts and springs and hello to auto layout contstraints.

But there are some itches with auto layout that bit me while trying to layout a basic vocabulary input form: iVocabulary Add Word

These itches were:

  1. Defining constraints that resize the window was impossible in IB
  2. Getting the views in a NSDictionary did not work as intended (for me)

1. Defining constraints that resize the window
This form contains three text fields, that all can change their size when the font changes. The user can choose font and size for each of the input fields.

The desired behaviour is: the window resizes and all textfields below the changed textfield move.

First, I tried to define the constraints using Interface Builder, but that didn’t work. IB tried to be extremely smart and removed some constraints as soon as I added the last contraint (between the comment field and the surrounding view). My plan was to add constraints between all views in the vertical order.

In my second and successful attempt I used ASCII art in windowDidLoad. The ASCII-Art was as easy as: V:|-[delimiterInfo]-[sourceField]-[targetField]-[commentField]-50-|

The result is nice and behaves as expected: Word input
form

The methods -[UIView addConstraints:options:metrics:views:] view parameter expects a dictionary containing the views mapped to the keys used in the ASCII-art. There is a macro you can use to get this dictionary by binding names: NSDictionaryOfVariableBindings but that didn’t work for me, because my ivars had a prefix and I didn’t want to specify that prefix in the ASCII- art. That lead to itch #2:

2. How to get a NSDictionary containing the views
In IB you can set an identifier for each view:

So my thought was: why not use that identifier to specify the views. Therefore I created a category for NSView containing one method:

- (NSDictionary*)dictionaryWithSubviewsByIdentifier
{
    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:self.subviews.count];
    [self.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSView *view = obj;
        if (view.identifier) {
            [dict setObject:view forKey:view.identifier];
        }
    }];
    return dict;
}

This way you can define the identifier in IB and you don’t need to have a binding for each and every view you want to use in your ASCII-art.

UPDATE:
Like Ken says in the comment: you don’t need this hacks, you simply need to define the right constraints. The trick is: define all vertical pins (aka constraints) from top to bottom and all works like a charm. Thanks Ken!