Auto Layout: Making sure you go to space today

Like many developers, I’ve skirted round Auto Layout; intrigued by the potential, mildly burned by Interface Builder, a little anxious about the forthcoming removal of choice when iOS 8 ships…

I’ve used Auto Layout, entirely in IB, in LocalTalk. I felt it was important to embrace and adopt Auto Layout fully. I wouldn’t say it helped me feel confident in it or in my understanding of it, but it’s a shipping app and that’s something. But it didn’t satisfy my feeling I was missing the point of auto layout. It didn’t make me a believer. 

Recently I’ve been working on a project, a modular addition to an existing static framework, that required some different layout handling between both orientations and devices. It’s also built for iOS 7 and upwards and felt like an ideal opportunity to revisit Auto Layout (henceforth AL). 

 

Approach

My starting point was two XIB files, one per device. I think this has become the natural assumption for the starting point for AL. If we can make it work in IB then we can understand enough to then move on to the programmatic version, right?
It’s how we started out when we were all chimps-around-the-monolith in Project Builder for Mac and the first incarnations of the iOS SDK in Xcode.
Dutifully I built up the constraints for both devices, occasionally cursing at how fiddly it is to select individual constraints with zero point gaps (Radar created). So far, so very good. 

 

Moving the goalposts

Build. Run. Test. Things are behaving, I’m fortunate enough to have real test data not lorem ipsum text and it’s looking good.
I’m enjoying trying to work out how little I can actually specify in IB, for the constraints to still work as expected. Generally you can throw in the kitchen sink then pare down to the minimal set.
Two things then happen.
Firstly the designs change. The existing design is a top-to-bottom stack of image, controls, labels. The new landscape design puts the image to the left and the buttons and labels to the right and in a different layout. After some fiddling about, it’s apparent that manipulating constraints in the XIBs isn’t going to cut it, I don’t have the flexibility. It’s not iOS 8 time yet and there’s no Size Classes to save the day.
OK, I’ll make a second pair of XIBs for the landscape layouts and switch them on rotation (the sound of much typing and clicking). Build. Run. Test. Rinse. Repeat. Done!
Yes there are some orange arrows and lines complaining about constraint conflicts but it works, reliably.
Then the second thing happens.
I’m building this in a basic project template so I can abstract the construction from the integration into the framework. I am then reminded that I can’t use XIBs as I can’t include binary files in the static framework.
I’m going to need to do all this programatically, aren’t I? I’m suddenly quite sleepy… (insert nap here) 

 

Burning the goalposts to the ground 

My first port of call was the documentation. I was expecting a Tolstoy-esque volume to wade through, and was pleasantly surprised to find the PDF version runs to only 46 pages. I recommend you go read it too.
Using this I was able to rebuild the entire module to use programmatic auto layout. There was trial and error. There was a lot of learning. But it’s a shallower learning curve than you’d think. Once we get iOS 8 I think getting the hang of traits and size classes will be enough of a challenge so knowing AL first will be an advantage.
I came away with a conclusion and some lessons learned. I attach them for the good of the common knowledge… 

 

You *May* Not Go To Space Today

Order

The order you do things matters. A lot. Auto Layout is dependent on a fully realised view hierarchy.
Add constraints to a view, relative to another view, not in the same hierarchy and you will not go to space today.
My preference is to make a method that is called directly in the init of the view controller that does the following:

  •  Alloc/init every view-based object with the minimal info you can, usually just with a frame of CGRectZero.
  •  Add every view-based object to it’s superview, all the way down the proposed hierarchy.
  •  Make a property for an NSDictionary and populate it, using NSDictionaryOfVariableBindings(), with all of your view-based objects as parameters, it’ll generate keys from the view names in the form '_name'. You will need this when building the constraints later and also to speed up some of the the things you’ll need to do to make it all work.
     

Out with the old

You can implement everything correctly for Auto Layout and still not go to space, unless you do one simple thing first.
By default, at least in iOS 7 and below, every object that is a UIView derivative has it’s translatesAutoresizingMaskIntoConstraints: property set to YES.
Cocoa verbosity makes the purpose of this property, and the problems it can cause, reasonably apparent, but for clarity let’s say that the old springs-n-struts model is active unless you turn it off and it can result in constraints added automatically that conflict with your own. You need to set this property to NO to prevent this.
Now the dictionary of views we prepared earlier comes into it’s own; you can enumerate through the values and set the property without repeating ones self.  

 

Two-way street

Now you’ve got the view hierarchy in place, it’s worth recapping how constraints are applied in this context.
In short, you create one or more constraints for a view, you add those constraints to it’s superview.
For example:

UIView *infoView = [[UIView alloc] initWithFrame:CGRectZero]; 
[infoView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:infoView];
NSArray *someConstraints = 
[NSLayoutConstraint constraintsWithVisualFormat:@“|-10-[_infoView]-10-|" 
                                        options:0 
                                        metrics:nil 
                                          views:@{@“_infoView”:infoView}];
[self.view addConstraints:someConstraints];

This will make a view with a zero frame and turn off any autoresizing madness. We get an array of constraints produced by the format string (more on which below, just go with it for now) for the view and then we add those constraints to it’s superview.

So far, so simple. The subview will resize to the width of the superview with a 10pt inset on the left and right.
If you wanted to also constrain the vertical alignment you could add another line that returned an array of constraints for the format string @“V:|-10-[infoView]-10-|” which would have the subview also resize vertically to fit in the superview with a 10pt inset at each side. (The “V:” indicates vertical. The matching H: is usually missing, it’s absence implies you mean horizontal).

What if the content of the subview is variable and could cause the subview to need more room than the superview provides?
The subview could be a UITextView and the superview the contentView of a UITableViewCell. How to handle this?
Well the good news is that the applicability of constraints between sub- and superview is a two way street.
You’d tie the subviews top, left and right edges to the superview and then tie the *superviews* bottom edge to the subview. This is in fact exactly how you can have dynamically sized tableview cells. See WWDC 2014 Session 216 for more on this.

One last thing. Note that the last parameter is a dictionary containing key:value references to the views involved in the constraints. If you miss out any of the partaking views from the list then you will not go to space today. This is the other reason why we made a dictionary back in the initialisation stage. You can just use that dictionary every time you need to provide the views. Extra views will be ignored but you’ll never forget to include one you need.  

 

Choose your weapon

AL can result in a lot of lines of code. You can use whatever method you want to abstract this out, but at the least you should probably have a dedicated method for it, if not a separate helper class, that gets called right after the view hierarchy is built, still from inside the init method.
If you’ve read the docs, you’ll know that there are two ways to create constraints, and we’ve already used seen of them.
That one returned an array of constraints based on a Visual Format Language string. The other will return a single constraint, based on the expression of one view in relation to another.
And that last is the important point so let’s recap it:

A constraint references the relationship of one attribute of one view to one attribute of zero or one other views.

A visual format string can reference as many views as you have available, specifying dimensions, spacing and order. As such it’ll return an array of constraints that combine together to fulfil the expressed desire of the format string.
However those format strings have limitations: They can’t express relations in terms of equality.
For this we use a statement that returns a single constraint which specifics, for the views concerned, the attributes for comparison, the relation to use as the basis for comparison, a multiplier to apply and a constant.

Let’s look at some examples of this:

[self.view addConstraint:[NSLayoutConstraint 
      constraintWithItem: infoView 
               attribute: NSLayoutAttributeCenterX 
               relatedBy: NSLayoutRelationEqual 
                  toItem: self.view 
               attribute: NSLayoutAttributeCenterX 
              multiplier: 1.0 
                constant: 1.0];

This will ensure that the subviews center.x value is always the same as the superview center.x value. It will be horizontal centred.

Let’s try another:

[self.view addConstraint:
[NSLayoutConstraint constraintWithItem: rightButton 
                             attribute: NSLayoutAttributeLeft   
                             relatedBy: NSLayoutRelationEqual 
                                toItem: leftButton 
                             attribute: NSLayoutAttributeRight 
                            multiplier: 1.0 
                              constant: 20.0];

This will place the left edge of rightButton 20pts to the right of the right edge of the left button.
This is a case that is also expressible in Visual Format Language so let’s see what that would look like:

[self.view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:@“[_leftButton]-20-[_rightButton]” 
                    options:0 
                    metrics:nil 
                      views:self.dictionaryOfViews]];

The point is that you can choose your weapons appropriately. You can use both forms when setting up the constraints, picking the one you need. As a reminder, Visual Format Strings allow you to declare multiple constraints in less code, but are unable to represent every relationship you may want to express. In which case use the single constraint constructs. 

 

Forests and Trees

I’ve found it makes for more readable code to start with the subviews of self.view, define their constraints, append these to a mutable array and then add them en masse to self.view.
Then for each subview repeat for it’s subviews and on and on.
This way you ensure you don’t trip over hierarchy issues and you can easily block out sections when debugging. The technique of adding constraints to arrays has another use and I’ll amplify on that in the last section on rotation.

The takeaway from this section is to always proceed from the general to the specific. First define constraints that apply to subviews in any orientation or device environment and then work towards special cases... 

 

Helter-Skelter

…like rotation.
I’m going to outline how I handled it in an iOS 7-only app that has a considerably different layout between portrait and landscape. Your mileage may vary but hopefully you’ll go to space today.
In my constraints-building method, for the constraints that are affected by the difference in layout between orientations, I actually make two sets, and assign each to it’s own mutable array, these arrays are declared up in the init stage as properties of the view controller. 
I then add the array of constraints that match the current device orientation.
Lastly, in the callback methods for rotation, I conditionally remove the old orientation constraints and add the new ones, like so:

if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
    [self.view removeConstraints:self.portraitConstraints];
    [self.view addConstraints:self.landscapeContraints];
}
else {
    [self.view removeConstraints:self.landscapeContraints];
    [self.view addConstraints:self.portraitConstraints];
}

One great thing you can do is place this 'remove/add dance' inside a UIView animation block to make the change a nice smooth one. 

 

Conclusion

If you’ve ever spent time answering Stack Overflow questions, you’ll know that a considered and well-phrased answer to a broad question can be the gift that keeps on giving reputation points. I have one such answer that I posted to the question: “When should I use IB and when should I use code?”.
My answer was that IB is an amazing, timesaving tool once you know what it’s doing for you. To appreciate it, practice doing it in code. It gets a decently regular upvote.
So why do we, more topically, why did *I*, not follow this advice when I wanted to start Auto Layout?
We all leap straight for IB and are surprised when we get burned.

Take the time to learn to how to do it entirely programmatically. Once you do you’ll feel more confident with what’s happening with what IB is doing in the background. And you will go to space today.

What makes watch tick?

Multipeer Networking @ NSLondon