My Style - Auto Layout Part 1

When you spend most of the day, every day, programming in Objective-C and working on iOS apps, you take for granted your personal work flow. I certainly wouldn't try to tell somebody to adopt everything that I do, but there are approaches I take that I feel could help people.

I started app development as iOS 6 was released and so went straight into Auto Layout and hardly looked back. I have since done a lot of manual work with frames, and even learnt all about memory management (a concept not foreign to me after my C++ background). With this full embrace of Auto Layout though I have implemented it in a ridiculous amount of alternate ways, and my most recent method has been by far my favourite so far.

Here I will go over the issues I have had with Auto Layout in the past. There can be odd issues with this API, almost solely due to the detachment from creating the constraints and having them applied. In the next post I will go over my implementation of this fantastic tool, because it is a brilliant framework, but for now I think it is apt to lay out the problems than can arise.

I’ll preface any further explanation with my thinking behind these posts. The only reason that n article like this can / needs to exist is because of the fragility of timing when it comes to view layout. The worst problems to encounter in iOS development are the ones that involve doing something at the wrong time in the view lifecycle or initialisation process. Auto layout, when used outside of .xib files, is a great example of how easy it can be to encounter unexpected issues based on where you put certain things.

Problems with Auto Layout in a UIViewController

Top and Bottom Layout Guides

Perhaps the most awkward place to correctly add NSLayoutConstraints. A lot can go wrong when using Auto Layout in a UIViewController.

The more infuriating issue I have encountered so far involves the bottomLayoutGuide and topLayoutGuide. First look at this implementation of the updateViewConstraints method where we position a table view in it’s superView. Assume the tableView has been initialised and added to the view correctly.

- (void)updateViewConstraints
    [super updateViewConstraints];

    //  remove all constraints
    [self.view removeConstraints:self.view.constraints];

    //  objects to be used in creating constraints
    NSDictionary *viewsDictionary       = @{@"tableView" : tableView, @"top" : self.topLayoutGuide };
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[tableView]|" options:kNilOptions metrics:nil views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@“V:|[top][tableView]|" options:kNilOptions metrics:nil views:viewsDictionary]];

After watching the WWDC 2012 videos as soon as they were released I began experimenting with auto layout. This seemed the sensible way to do it. Every time the view would need laying out, this method would be called, and so surely this was where I should put my constraints. It worked fine until iOS 7, and then I got screwed.

Obviously in the above method I am using the topLayoutGuide. A handy new iOS 7 property on UIViewController’s which allow for correct offsetting in table views and collection views when using a navigation bar. The same goes for the bottomLayoutGuide when using a UITabBar or equivalent. This is down to the translucent property that has been introduced on this bars. For more on iOS 7 development my favourite resource is the iOS 7 by Tutorials book available at RayWenderlich.

Anyway, with this topLayoutGuide, it turns out that it is a constraint like any other, and as such when making the sending the suggested message of:

[self.view removeConstraints:self.view.constraints];

You are in fact removing these layout guides permanently, and so any use of them in the future, such as:

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@“V:|[top][tableView]|" options:kNilOptions metrics:nil views:viewsDictionary]];

Will simply result in the top layout guide having a rect of 0.0f, 0.0f, 0.0f, 0.0f and therefore it is useless. The awful way around this is to loop through the constraints and, as I have done it the past, check with constraint has the firstItem or secondItem equal to the topLayoutGuide and skip removing it. For the better approach, you’ll need to check out part 2 of the auto layout guide series where I explain how I got about these sorts of things.

Animating Constraints

Perhaps the most lacking aspect of auto layout is animation. With the option being add or remove your constraints and then call:

[UIView animateWithDuration:1.0f delay:0.0f options:kNilOptions animations: ^{ [self.view layoutIfNeeded]; } completion:nil];

you’re very clearly limited. You’re instinct may be to add options to the animation to see how this changes it, and quite frankly, it doesn’t. There are no options when dealing with auto layout, and this is at the core of why I have problems with it.

If you are unaware as to what will happen in the case of the above call to animate the layout, the view whose constraints have been altered in some way will animate smoothly from it’s current state, to the updated one. That is the only course of action you have.

This causes problems when you want to fade in a subview added with constraints, because if you initialise a view like so:

UIView *view = [[UIView alloc] init]; view.backgroundColor = [UIColor redColor]; view.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:view];

Position it with auto layout:

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[view]-|" options:kNilOptions metrics:nil views:@{@"view" :view}]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[view]-|" options:kNilOptions metrics:nil views:@{@"view" :view}]];

And then attempt to animate it into position with the method call above, you will face the obvious issue of the view animating from it’s current state, into the updated, laid out with constraints, state. This means the view (currently with a default frame of 0.0f, 0.0f, 0.0f, 0.0f) will expand from the top left corner into position.

Unless you want every view expanding from the top left corner into it’s constrained position, you will have to set an initial, manual frame, add the constraints, and then animate it all. As explained before, everything else will have to be done in spite of the constraints, and you will have to work around the timing of the whole thing.

You may be thinking that you should add the constraints and then do the standard view.alpha = 0.0f, view.alpha = 1.0f animation. This is all well and good if you can make sure you time it in such a way where you be certain the view is in the correct position. I have already talked about how adding the constraints to the view does not mean that they have been applied yet, and you will have to wait until the layout pass has been made, or you can force it yourself with [self.view layoutIfNeeded]; and then do all you layout animations like so:

- (void)viewDidLayoutSubviews
    [super viewDidLayoutSubviews];
    //  layout animation code

For me though this is not a pleasant solution at all. Encapsulating all your disparate animations in this single method is not exactly nice. I would like to say here that I will show you a brilliant way to overcome the auto layout animation limitations in the next post, but to be honest my methods are currently also fairly, if a little less, unpleasant.

Wrap Up

These are some problems I have encountered and have mentioned here in the hopes that if / when you encounter them, you know what is going on. I can tell you for a fact then when I was dealing with the topLayoutGuide it took too much debugging to figure out exactly what going on. As soon as I write up the next part of this auto layout mini-series I will post it. I hope this helps somebody.