A Developing Story

by Justin Driscoll

My name is Justin and this is my story. I write about software development, product design and technology in general. I also design and develop web and mobile software as both the lead developer of Makalu Interactive and the founder of Retrobit Operations. You can find me on both Twitter and App.net.

Auto Layout for iOS Revisited

Posted on May 18, 2013

It's now been four months since I posted Working With Auto Layout on iOS and Rego is now up to version 1.2. It hasn't been perfect and there have been some questions about the performance of auto layout on iOS, but I still believe the benefits outweigh any negatives.

Now that I have a few more months experience using auto layout in Rego I'd like to follow up my original post with some additional thoughts on the technology, how one might use it, and what can, and will, go wrong.

Why Don't We Do It in the Code?

A lot of the negativity towards auto layout is more focused on it's integration with Interface Builder than the underlying constraint system. Not only does Interface Builder silently add constraints to keep your layouts valid, it breaks outlets and makes stupid guesses about what you really want. But even though I have my own complaints I think the benefits of working with Storyboards balances out many of these issues. That said, there are times when defining parts of your layout outside of IB is great option.

Rego Main View

For example, while most of the main view in Rego is defined in the application's main storyboard file, some of the elements are configured in code. In some cases this is because these elements are positioned similarly on multiple views and updating their constraints in just one place in the code is easier than updating the storyboard in three different places. In others it's simply because the ideal constraint set needed to position them would not be possible using Interface Builder alone. On top of that, with these minor elements positioned in code the storyboard layout is simplified and easier to modify.

Here's the code from Rego that sets up the constraints for the settings and new place buttons on the main view:

- (void)setupLayoutConstraints
{
    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_addPlaceButton, _settingsButton);

    for (UIView *view in [viewsDictionary allValues]) {
        view.translatesAutoresizingMaskIntoConstraints = NO;
    }

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[_settingsButton(46)]"
                                                                      options:0
                                                                      metrics:nil
                                                                        views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_settingsButton(46)]"
                                                                      options:0
                                                                      metrics:nil
                                                                        views:viewsDictionary]];

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[_addPlaceButton(46)]"
                                                                      options:0
                                                                      metrics:nil
                                                                        views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_addPlaceButton(46)]"
                                                                      options:0
                                                                      metrics:nil
                                                                        views:viewsDictionary]];

    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_settingsButton
                                                          attribute:NSLayoutAttributeLeft
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.mapView
                                                          attribute:NSLayoutAttributeLeft
                                                         multiplier:1.0
                                                           constant:3.0]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_settingsButton
                                                          attribute:NSLayoutAttributeTop
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.mapView
                                                          attribute:NSLayoutAttributeTop
                                                         multiplier:1.0
                                                           constant:3.0]];

    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_addPlaceButton
                                                          attribute:NSLayoutAttributeRight
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.mapView
                                                          attribute:NSLayoutAttributeRight
                                                         multiplier:1.0
                                                           constant:-3.0]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_addPlaceButton
                                                          attribute:NSLayoutAttributeTop
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.mapView
                                                          attribute:NSLayoutAttributeTop
                                                         multiplier:1.0
                                                           constant:3.0]];
}

As you can see, there is one major downside to setting up your constraints in code: There's a lot of typing involved. There isn't even anything clever going on here. I'm just telling auto layout that each button is 46 points tall and 46 points wide and should be positioned 3 points from the top and 3 points from either side of the map view.

This is mostly a matter of taste but I like to set up all my constraints at once in a single method (and then bury that at the bottom of the source file so that I don't have to look at it anymore). When this method is called I first initialize a new dictionary with every view that will be receiving constraints using the NSDictionaryOfVariableBindings function. Then I loop through all the values in this dictionary and set translatesAutoresizingMaskIntoConstraints to NO on each view. In this example it doesn't save much typing but I prefer to do as much as possible using the ASCII-art shorthand and then fill in any missing constraints using the addConstraint method.

Two things to look out for when defining your constraints in code:

  1. Don't forget to set translatesAutoresizingMaskIntoConstraints to NO on every view you're going position in code.
  2. You have to add your views to the view hierarchy before you set their constraints or your won't get very far when you try to run your app.

Defining your auto layout constraints in code can be useful to avoid repetition or simplify your storyboards. In some cases it may be the only option to achieve your desired layout. I believe the trick is in knowing when to work within the limitations of Interface Builder and when to retreat into code.

It's a Trap!

If you do decide to use Interface Builder to set up your auto layout constraints there are a number of pitfalls you need to be aware of. This isn't an exhaustive list by any means, but these are the thing I found either the most frustrating or difficult to diagnose.

Constraints Within Custom Prototype Cells

Prototype table view cells are one of my favorite features that were introduced with storyboards. They really deliver on the Cocoa promise of making simple things simple. If you set up a custom prototype cell using auto layout however, you'll notice that the automatic cell resizing (when editing) is totally broken. This happens because while any views you add to the cell are correctly added as subviews of the cell's content view, the constraints are tied to the cell itself. The net effect of this is that when your content view resizes in response to state changes your subviews will not respond as you probably thought they would. This has to be a bug and hopefully one that will be fixed soon. The only solution I know of is to loop through every subview of your cell and replace the constraints that have the cell as a target with an identical constraint targeting the content view.

Here's the code I'm using (based off of this answer):

- (void)moveConstraintsToContentView
{
    for (NSInteger i = self.constraints.count - 1; i >= 0; i--) {
        NSLayoutConstraint *constraint = [self.constraints objectAtIndex:i];

        id firstItem = constraint.firstItem;
        id secondItem = constraint.secondItem;

        BOOL shouldMoveToContentView = YES;

        if ([firstItem isDescendantOfView:self.contentView]) {
            if (![secondItem isDescendantOfView:self.contentView]) {
                secondItem = self.contentView;
            }
        }
        else if ([secondItem isDescendantOfView:self.contentView]) {
            if (![firstItem isDescendantOfView:self.contentView]) {
                firstItem = self.contentView;
            }
        }
        else {
            shouldMoveToContentView = NO;
        }

        if (shouldMoveToContentView) {
            [self removeConstraint:constraint];
            NSLayoutConstraint *contentViewConstraint = [NSLayoutConstraint
              constraintWithItem:firstItem
                       attribute:constraint.firstAttribute
                       relatedBy:constraint.relation
                          toItem:secondItem
                       attribute:constraint.secondAttribute
                      multiplier:constraint.multiplier
                        constant:constraint.constant];

            contentViewConstraint.priority = constraint.priority;

            [self.contentView addConstraint:contentViewConstraint];
        }
    }
}

Outlets Will Be Disconnected Without Warning

This one is pretty self explanatory. If you define an outlet for a layout constraint and Interface Builder changes your layout it will happily delete that constraint and leave your outlet unconnected. At least Xcode has these little outlet indicators you can keep an eye on.

Align Baselines Should Die in a Fire

For some reason Interface Builder loves to align baselines. It loves aligning baselines almost as much as it loves deleting your outlet connections. If you position two views horizontally next to each other it will more than likely try to set an align baselines contraint on them. This might not be a big deal but aligning baselines causes weird display issues like squished button images. If you're using auto layout and you can't figure out why your buttons look like crap look for an align baselines constraint.

Up Is Not Always Up

This one is hard to even explain but I'll do my best. When you have a contraint representing a non-zero distance between two views it has a direction. While this direction is not displayed in the inspector, it decides which way the view will move when you modify the constraints constant. So for one constraint type increasing the constant will move it up in relation to the other view and for another it will move it down (or left and right). This is especially fun to discover when working with animations.

Let's End It On A High Note

Auto layout is pretty great. Building Rego for both screen sizes using springs and struts would have been a lot more work and I'm looking forward to speaking with an Xcode engineer at WWDC next month about the improvements they're making in iOS 7.

Previously on A Developing Story...

A Developing Shirt

Posted on May 5, 2013

I'm tired of promoting other people with my clothing. I just need nine more suckers to order one of these rad shirts and they'll actually get made.

A Simple Custom View That Supports UIMenuController on iOS

Posted on April 23, 2013

UIMenuController is the class you interact with to present the pop up menu most commonly used for copy and paste in iOS. I Recently found that adding a menu to my own UIView subclasses was easy, but not entirely obvious at first, so I thought I'd post an extremely simple example based on what I learned.

Adding Full Text Search to a Core Data App Using SQLite

Posted on April 14, 2013

Full text search is one area where the base iOS SDK doesn't have a lot to offer. You can get something that looks kind of like full text search NSPredicate but it becomes really slow with larger data sets. Here's one way to add full text search to a new or existing Core Data app using SQLite FTS4.

Announcing Rego for iOS

Posted on February 2, 2013

Makalu's new app is coming this month to an iPhone (or iPod Touch) near you.

One Week With AppCode

Posted on January 26, 2013

On Monday I decided to try using AppCode for an entire day. Then one day turned into a week.

Working With Auto Layout on iOS

Posted on January 12, 2013

After spending the last few months working on a new app using Cocoa Auto Layout for iOS I thought I'd share some things I learned along the way.

OnePAD Website Redesign

Posted on December 28, 2012

I redesigned the OnePAD website and upgraded it from static files to the Middleman static site generator.

Going off the Book

Posted on December 2, 2012

There’s a moment in chess when a game deviates from a well known sequences of moves each player has memorized. They call this going "off book" and it's an interesting metaphor for modern software development.

Communicating with Blocks in Objective-C

Posted on September 3, 2012

I love blocks because blocks make Objective-C much more expressive. They can also reduce the amount of code you need to write, which reduces the amount of code you need to maintain and debug. Any developer who has ever worked in a higher level language like Ruby, Python or Javascript should feel right at home using blocks. Once they get past the awkward syntax at least.

OnePAD Version 1.3 Available Now

Posted on August 11, 2012

OnePAD version 1.3 has been approved for sale and brings bug fixes, better browsing and a brand new icon.

Lights, Camera, OnePAD

Posted on July 1, 2012

I finally got around to putting together a quick screencast for OnePAD, my iOS daily notebook app.

The iPad Split-Keyboard and (Missing) Notifications

Posted on May 19, 2012

When the iPad keyboard is "un-docked" or "split" your app won't get the same UI events. If your app needs to accurately track the visibility of the keyboard on iPad you need to do a little more work.

iOS Icon Template for Sketch 2

Posted on May 5, 2012

I put together a quick iOS icon template for Sketch 2. It's not complete but it includes templates and slices for the iPhone and iPad and the large App Store icon.

Core Data with a Single Shared UIManagedDocument

Posted on March 7, 2012

UIManagedDocument is a great way to set up a Core Data stack for your application. With application delegates taking a smaller role in newer applications, sharing the document's managed object context can be tricky.

UIManagedDocument, Nested Contexts and Notifications

Posted on December 28, 2011

UIManagedDocument is great. It wraps up an entire core data stack into a nice little package. But like most new APIs, there are a few little details that can trip you up if you're not careful.

Cleaning Up

Posted on November 27, 2011

I’ve spent several hours this morning performing some long-overdue online identity maintenance. Like many of you, I’d imagine, I’ve accumulated a trail of neglected blogs, web sites, unused domain names and forgotten accounts. There’s still a lot to do to get things where I’d really like them to be, but it feels good to have at least tackled the worst of it.

JCDHTTPConnection

Posted on November 26, 2011

JCDHTTPConnection is a pretty simple class that takes a NSURLRequest object and (up to) three block callbacks. It’s asyncronous, of course, and much more convenient than setting up a delegate. I’m pretty happy with it, and I hope it might help someone out.

ImageKit Has Moved

Posted on February 26, 2011

I’m very happy to announce that the ImageKit project has acquired a trio of new maintainers.

SuperDuper and Hazel's App Sweep

Posted on January 8, 2011

If Hazel’s App Sweep function stops working for you, a backup drive (or folder) might be the problem. Hazel automatically ignores Time Machine volumes but other backups, such as a Super Duper clone, can trick Hazel into thinking the App is still installed.

Geocoding Forms on Submit with Javascript

Posted on November 17, 2010

The Google Maps API offers an exceptional geocoding service. It’s accurate, reliable and well documented. However, on shared hosting environments (such as Google’s own AppEngine), rate-limiting can become an issue quickly.

The iPhone, IMAP and Multiple "From:" Addresses

Posted on December 30, 2009

I currently manage several email addresses through a single Gmail account, and apart from Google’s creative interpretation of the IMAP protocol, it’s been grand. I can send and receive messages to or from any of my accounts from a single, virtually spam-free, inbox. I wanted this ability on my iPhone as well but the setup is not as straightforward as it could be. Here’s what worked.