Search
Rich's Mad Rants
Powered by Squarespace

Entries in iOS (31)

Thursday
Aug292013

Just a quick update...

Summer is always an awkward time for me. I get so wrapped up with the new iOS SDK, but I cannot actually write about any of it yet. Nor can I really present the new information in my classes. It's frustrating, but also exciting. There are some cool things coming.

In the meantime, I continue to work away at the second edition of my book. It's shaping up well, and has a lot of great new information about iOS 7 in it. 

I will also be running a pre-conference workshop at MacTech Conf on implementing iCloud. This is a full-day, hands on workshop. We will take a sample project and implement all three types of iCloud syncing: key-value syncing, document syncing and even Core Data syncing. We will be looking at the strenghts and weaknesses of each approach, and dig into complex topics, like conflict resolution and duplicate entries.

More importantly, I will cover all the new tools and improved APIs for iOS 7. Unfortunatley, I can't say much about them yet--except to reassure everyone. Apple is continuing to advance and improve their iCloud support--and the changes they've made will make life much easier for everyone.

There are still seats available, if you're interesting in coming. The workshop will be on November 5th, at the Manhattan Beach Marriott, Los Angeles, CA. You can find more information at the MacTech Conference web site. Please spread the word to anyone who might be interested.

Monday
Mar042013

Snapshotting Data For Background Saves

Sometimes things are so obvious, I just can't see them.

Last week, while teaching a class, I was trying to convince my students to always save and load data on a background thread. Mostly, I was trying to introduce them to Grand Central Dispatch, and convince them that it was a convenient way to bounce a task onto a background thread, then bounce the results back to the main thread. This--in theory--would allow us to do all our UI changes on the main thread. More importantly, if I only access my classes properties on the main thread, I can safely use nonatomic properties.

After all, atomic properties are slow, and they only handle a limited number of threading issues. Sure, it prevents my getter and setter from clobbering each other--but code that operates at a higher level may still not be thread safe. For example, what happens if I have a method that gets the value from my property, then I modify that value and set it again. Even though the accessors are thread safe, I'm going to have problems if my threaded code interleaves two of these method calls. Best to just keep all access on the main thread.

So, I introduced the following code:


- (void)save {
    
    NSArray *books = [self.books copy];
    
    dispatch_queue_t backgroundQueue = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(backgroundQueue, ^{
        
        [NSKeyedArchiver
         archiveRootObject:books
         toFile:[[self documentURL] path]];
    });
}

That's when one of my students pointed out that this protected the self.books array, but we could still have threading problems with the individual Book objects. Now, in this case it didn't matter. Our Book objects were immutable (yet another reason to use immutable objects when possible), but how should we handle this in the general case?

Writing code to make a deep copy of the array seemed like the correct answer--but that could rapidly get tedious--especially if you had a complex object graph. In the back of my head, I knew there had to be a simpler answer--I knew that I'd used a simpler solution many times in my own code--but I couldn't put my finger on it. Not until this morning.

Of course, the easiest way to snapshot this data is to break the save up into two steps. First, use the NSKeyedArchiver to serialize it into an NSData object, then save the data object on a background thread. The best part is, this reuses the serialization code we've already written, so it doesn't require any additional code. This is one of those things that is obvious in hind-sight. Still, I'm not sure how many people are aware of it. The code is shown below:



- (void)save {
        
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.books];
    
    dispatch_queue_t backgroundQueue = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(backgroundQueue, ^{
        [data writeToFile:[[self documentURL] path] atomically:YES];
    });
}

The in-memory operation to create the NSData object should be fast--so there should be no problem doing this on the main thread. Saving to disk can be slow, so we simply move that portion off to the background. Of course, we could run into problems if we have a massive amount of data--since we'll be essentially making two copies of it in memory. However, this would be true even if we implemented our own deep copy. The bottom line is, if we're reading/writing a massive amount of data, we need to look into other solutions (e.g. Core Data or NSFileWrapper).

Saturday
Dec292012

Understanding Auto Layout

The user interface is the main, driving feature of most iOS applications. It is our primary means of communicating with our users, and in many cases, their opinion of our apps is heavily influenced by their experience with our UI. Therefore, it’s worth taking the time and effort to ensure our interfaces are attractive, responsive and (above all else) highly useable.

Part of building a high-quality user interface includes controlling how they respond to changes in our view’s size. These changes may occur for a number of reasons--most obviously, when the user rotates the device. If the interface supports auto-rotation (and all applications should. Few things frustrate me more than an application that refuses to let me choose how I hold my device--Yes, I’m looking at you, Flipboard for iPhone) then we must adjust our layout as the view stretches from portrait to landscape mode and back.

Somewhat surprisingly, other things can also cause variations in view size. In some cases, it may be device driven. For example, opening an app on the iPhone 5 requires an additional 88 points of height (or 176 actual pixels). System events can also drive changes. For example, if you have an active phone call in the background, the system will display a green bar across the top of the screen. This shortens our display by another 20 points. The system similarly displays a red bar when recording audio.

Bottom line, we need to make sure our user interface adapts to any possible size or orientation. There are 3 basic approaches. We can hard code the position and size of our subviews--programmatically adjusting them whenever our root view’s size changes. Alternatively, we can use the view's Autoresizing mask to define how our user elements will resize and reposition themselves in response to these changes. Finally, we can define our view’s layout using Auto Layout constraints.

Programmatically Laying Out Views

By default, all views have a fixed height and width. Their location is also defined as a fixed distance from the top and a fixed distance from the left edge of their superview. We can see this in the view's frame property. It has an origin (x and y coordinates for its upper left corner) and a size (width and height). While this lets us specify an unambiguous layout for our controls--it doesn’t give us any flexibility. If the view’s size changes, we must re-layout all our interface elements, cycling through all our interface elements and repositioning and resizing each one.

Fortunately, this is fairly easy. We can reposition a view by modifying either it’s center or frame property. We can also resize it by modifying either the frame or bounds properties. Of course, the calculations required to determine the new size and position are often somewhat complex.

If we want to respond to changes in our superview’s size, we will typically modify these properties in our view controller’s viewWillLayoutSubview or viewDidLayoutSubview method. We can also implement a custom view’s layoutSubviews method to fine tune the layout of any of its subviews.

Autoresizing

While programmatically repositioning one or two subviews isn’t too hard, this quickly becomes unwieldy for complex user interfaces. Ideally, we would like to automate some, if not all, of this work. In many cases, we can do this by simply setting each view's Autoresizing mask.

Autoresizing lets us define 6 parameters that describe how a view will respond to changes in its superview size. These are the left margin, right margin, top margin, bottom margin, height and width. Each of these can be set to be either fixed or flexible.

Here are the rules: Fixed parameters have a set size. They will not change as the superview shrinks and grows. Flexible parameters, on the other hand, can shrink and grow. Left margin + width + right margin will always equal the superview’s width. Similarly top margin + height + bottom margin will equal the superview’s height.

If you have more than one flexible parameter for a given dimension, the changes in that dimension will be split between them. On the other hand, the margins have a higher priority than the height and width--so if you set all three to fixed, the size parameter will be ignored, and the height (or width) will change with the superview.

This system has several advantages. It is relatively simple. Use it once or twice and you understand how it handles most layout situations. Its biggest problem, however, comes from this simplicity. It can only handle a few, simple layouts. Specifically, it works best when only one element in a row (or column) is flexible. Trying to manage multiple flexible elements tends to produce an interface that feels somewhat sloppy, as the space between elements grows and shrinks with the view’s size.

If we want more precise complex changes, we either need to manage the layout programmatically or switch to Auto Layout.

Auto Layout

Apple created Auto Layout to fill in the gaps left by Autoresizing. In theory, it should eliminate the need to programmatically lay out our view hierarchies. Auto Layout lets us define complex relationships between views, giving us a lot more power--but this power comes at a cost. Auto Layout is much harder to understand and setup than Autoresizing.

Instead of specifying a view’s position or size, we specify a number of constraints. These are represented as linear equations, as shown below:

Attribute_1 = Multiplier * Attribute_2 + Constant

The attributes can be any of the following:

Left Edge
Right Edge
Top Edge
Bottom Edge
Leading Edge
Trailing Edge
Width
Height
CenterX
CenterY
Baseline
Not An Attribute

Of these, three merit special mention. Leading edge is the first edge you run across based on the current local’s reading direction. For left-to-right languages like English, this would be the left edge. Trailing edge is the opposite; the right edge for English. By using the Leading Edge and Trailing Edge (instead of Left Edge and Right Edge), our UI elements will automatically reorder themselves when we switch from a left-to-right language to a right-to-left language.

Not An Attribute indicates that the attribute (and multiplier) will not be used in this calculation. We can use this to define constant constraints.

Here are some examples in pseudo-code:

// Example 1: Setting a constant height
View’s Height = 0.0 * Not An Attribute + 40.0;

// Example 2: Setting a fixed distance between two buttons.
Button_2’s Leading Edge = 1.0 * Button_1’s Trailing Edge + 8.0;

// Example 3: Make two buttons have the same width
Button_2’s Width = 1.0 * Button_1’s Width + 0.0;

// Example 4: Centering a view in its superview
View’s CenterX = 1.0 * Superview’s CenterX + 0.0;
View’s CenterY = 1.0 * Superview’s CenterY + 0.0;

// Example 5: Giving a view a constant aspect ratio
View’s Height = 2.0 * View’s Width + 0.0;

To use Auto Layout, we define a set of rules, and the system tries to fulfill them. Unfortunately, like most rules-based systems, each individual constraint may seem simple enough--but as they are combined, they may create unintended consequences. As the number of constraints grow, it gets harder and harder to accurately guess what the system will do at runtime.

Sufficient, Non-Ambiguous Layouts

To generate predictable results, we must declare constraints that are both sufficient and non-ambiguous. Typically this means we need at least four constraints per subview. One to define its Height, one to define its Width, one to define its x-coordinate and one to define its y-coordinate. Of course, there are many ways to skin this cat, but as long as we declare two of the possible attributes for each dimension, we should be fine. For example, to specify a view’s Y axis, we could specify the Top and Bottom, Top and Height, Bottom and Height, Center Y and Height or Baseline and Height. Other combinations are possible (Top and Center Y, for example), but these will be somewhat rare.

We also need to avoid assigning too many constraints--creating conflicts that cannot be simultaneously satisfied. For example, I cannot define a view’s Top, Height and Bottom. One of these will need to change as the superview’s height changes.

So far his seems fairly straightforward. Just create two and only two x-dimension constraints and two and only two y-dimension constraints for each subview in your view hierarchy. You should be good to go.

Of course, nothing about Auto Layout will ever be that straightforward. The problem is, I lied.

Actually constraints don’t have to be linear equations. They can also be inequalities. For example, we could define a minimum width for a view as shown below:

View’s Width >= 0.0 * Not An Attribute + 20.0;

Note: when you use inequalities, you will need more than four constraints to specify an unambiguous layout. To make matters worse, each constraint also has a priority. Priorities range from 1 to 1,000. If it’s set to 1000, the constraint is required. Anything less and the constraint is optional. The system will try to satisfy all the optional constraints in order from highest priority to lowest.

Finally, some controls have a natural size. Rounded rectangle buttons, for example, are typically 44 points tall and wide enough to hold their title text. Text fields, on the other hand, are 30 points tall, but don’t have a preferred width. We say that these views have an intrinsic size--and this generates a whole new set of constraints for us.

Intrinsic Sizes

We can set the intrinsic size of a UIView subclass by overriding its intrinsicContentSize method. This method should return a CGSize. This could be a fixed size, or a size calculated from the view’s contents. If the view does not have an intrinsic size for a given dimension, it should set that dimension’s value to UIViewNoIntrinsicMetric (-1.0). By default, UIView returns {UIViewNoIntrinsicMetric , UIViewNoIntrinsicMetric}.

If we need to recalculate the intrinsic size (for example, after changing the view’s content), we should call invalidateIntrinsicContentSize. The system will then use the new intrinsic size on the next layout pass.

Auto Layout creates four constraints based on a view’s intrinsic size; two pairs of inequalities per axis. The compression resistance maintains the view’s minimum height and width, while the content hugging defines its maximum. The linear equations are shown below.

// Compression Resistance
View’s Height >= 0.0 * Not An Attribute + View’s Intrinsic Height;
View’s Width >= 0.0 * Not An Attribute + View’s Intrinsic Width;

// Content Hugging
View’s Height <= 0.0 * Not An Attribute + View’s Intrinsic Height;
View’s Width <= 0.0 * Not An Attribute + View’s Intrinsic Width;

By default, the content hugging priority is set to 250, and the compression resistance priority is set to 750. We can modify the priorities for these constraints by calling setContentCompressionResistancePriority:forAxis: and setContentHuggingPriority:forAxis:.

This gives us considerable flexibility when it comes to our views. For a button, we might set both the compression resistance and the content hugging to a priority of 1,000. This would force the button to a fixed size. A text field, on the other hand, might have its content hugging priority for the X axis set to NSLayoutPriorityDefaultLow, all others set to 1,000. This would give it a fixed height and a minimum width, but would allow it to grow to fill the available space.

Specifying Constraints

There are four ways to set up a view’s constraints. We can simply use the default constraints. We can programmatically define each constraint. We can programmatically set multiple constraints using the Visual Format language. Finally, we can let Interface Builder manage the constraints for us.

I’ll talk about constraints and Interface Builder in a bit. For now, let’s look at doing it in code.

Default Constraints

Whenever we add a view to the view hierarchy, the system automatically generates a set of constraints to match the view’s Autoresizing mask. By default, all views have an Autoresizing mask with a fixed left margin, fixed top margin, fixed height and fixed width.

Sometimes these default constraints can prove useful. In many cases, it might be easier to define the Autoresizing mask, and use the auto-generated constraints, rather than creating your constraints by hand. Additionally, the default constraints let us continue to resize and move a view by modifying its frame, bounds and center properties. This can be particularly helpful when animating views (see Constraints and Core Animation for more information).

However, we will often want to use our own constraints--and that means disabling the default constraints. To disable these, simply call setTranslatesAutoresizingMaskIntoConstraints: and pass NO.

Note: Default constraints are automatically disabled for any views added using Interface Builder.

Constraint API

We can programmatically create our own constraint using NSLayoutConstraint’s constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant: method. This is a direct translation of the linear equation shown earlier.

The first two arguments define the view and the attribute that we are constraining. The attribute must be of type NSLayoutAttribute. NSLayoutAttribute is defined below:

enum {
NSLayoutAttributeLeft = 1,
NSLayoutAttributeRight,
NSLayoutAttributeTop,
NSLayoutAttributeBottom,
NSLayoutAttributeLeading,
NSLayoutAttributeTrailing,
NSLayoutAttributeWidth,
NSLayoutAttributeHeight,
NSLayoutAttributeCenterX,
NSLayoutAttributeCenterY,
NSLayoutAttributeBaseline,

NSLayoutAttributeNotAnAttribute = 0
};
typedef NSInteger NSLayoutAttribute;

Next we pass an NSLayoutRelation to define the equation's type. NSLayoutRelation is defined below:

enum {
NSLayoutRelationLessThanOrEqual = -1,
NSLayoutRelationEqual = 0,
NSLayoutRelationGreaterThanOrEqual = 1,
};
typedef NSInteger NSLayoutRelation;

The next two arguments define the view and the attribute for the right side of the equation. Again, the attribute must be an NSLayoutAttribute. These are followed by the multiplier and the constant (both CGFloats).

So, the following call:

NSLayoutConstraint *constraint =
[NSLayoutConstraint constraintWithItem:view1 
        attribute:attribute1 relatedBy:relationship 
                                toItem:view2 
       attribute:attribute2 multiplier:mx constant:c];


Defines the following linear equation:

view1.attribute1 view2.attribute2 * mx + c;

Once we have the constraint, we still need to add it to the system. Constraints should be added to the closest ancestor of both views in the constraint. By ancestor, I mean either the view itself, it’s superview, it’s superview’s superview, or any other superview moving up the view hierarchy.

For example, if I was creating a constraint between a view and it’s superview, I would add the constraint to the superview. If I was adding a constraint between two sibling buttons, I would add the constraint to their shared superview.

We add constraints by calling UIView’s addConstraint: method and passing our constraint.

The following code unambiguously defines a set of constraints for a button and a text view. In a left-to-right language (like English) the button should be a constant distance from the superview’s upper left corner. It also has a fixed width. The text field should be a fixed distance from the button’s right edge. It is also vertically centered on the button. Finally, the right edge of the text field should be a fixed distance from the superview’s right edge. Therefore, as our superview resizes, the text field’s width will grow and shrink to appropriately fill the space.

    // Setup Button’s Constraints
    NSLayoutConstraint *buttonX =
    [NSLayoutConstraint
     constraintWithItem:myButton
     attribute:NSLayoutAttributeLeading
     relatedBy:NSLayoutRelationEqual
     toItem:view
     attribute:NSLayoutAttributeLeft multiplier:1.0
     constant:20.0];
    
    [view addConstraint:buttonX];

    NSLayoutConstraint *buttonY =
    [NSLayoutConstraint
     constraintWithItem:myButton
     attribute:NSLayoutAttributeTop
     relatedBy:NSLayoutRelationEqual
     toItem:view
     attribute:NSLayoutAttributeTop
     multiplier:1.0
     constant:20.0];
    
    [view addConstraint:buttonY];

    // Setup Button’s Intrinsic Size Priorities
   [myButton
     setContentHuggingPriority:UILayoutPriorityRequired
     forAxis:UILayoutConstraintAxisHorizontal];
    
    [myButton
     setContentCompressionResistancePriority:UILayoutPriorityRequired
     forAxis:UILayoutConstraintAxisHorizontal];

    // Setup Textfield Constraints.
    NSLayoutConstraint *textX =
    [NSLayoutConstraint
     constraintWithItem:myTextField
     attribute:NSLayoutAttributeLeading
     relatedBy:NSLayoutRelationEqual
     toItem:myButton
     attribute:NSLayoutAttributeTrailing
     multiplier:1.0
     constant:5.0];
    
    [view addConstraint:textX];

    NSLayoutConstraint *textY =
    [NSLayoutConstraint
     constraintWithItem:myTextField
     attribute:NSLayoutAttributeCenterY
     relatedBy:NSLayoutRelationEqual
     toItem:myButton
     attribute:NSLayoutAttributeCenterY
     multiplier:1.0
     constant:0.0];
    
    [view addConstraint:textY];
    
    NSLayoutConstraint *textWidth =
    [NSLayoutConstraint
     constraintWithItem:myTextField
     attribute: NSLayoutAttributeTrailing
     relatedBy:NSLayoutRelationEqual
     toItem:view
     attribute: NSLayoutAttributeTrailing
     multiplier:1.0
     constant:-20.0];
    
    [view addConstraint:textWidth];


In many ways, these are the easiest constraints to understand. However, as you can see, they require a lot of typing. Fortunately, the Visual Formatting language greatly reduces this--but it can also obscures some of the details, making it somewhat harder to understand, at least until you wrap your head around it.

Visual Formatting Syntax

Hands up if you ever spent an afternoon “drawing” ASCII art. This feature is for you.

NSLayoutConstraint supports a visual formatting syntax for constraints. This allows us to “draw” our constraints using an ASCII-art like syntax. The full details can be found at Cocoa Auto Layout Guide > Visual Format Language, but I’ll go through the basics here.

We start by creating an NSString that defines a group of constraints. These strings will use the symbols shown below:

Symbol Meaning
| The edge of the containing superview.
[<view_string>]
[<view_string>(<width>)]
[<view_string>(<width>@priority)]

Specifies a view in our UI. Optional arguments can be used to declare the view’s width and the width’s priority.

The width and priority values can be literal numbers or constants. The width value can also be an inequality.

You can also set a view’s width equal to another view. Simply use the other view’s view string for the width.

Finally, we can create complex arguments by providing multiple width@priority pairs separated by commas.

-
-<width>-
-<width@priority>-

These represent the space between views. If no width is specified, the system will use standard spacing.

The width and priority can be a literal number or constant.

The width value can also be an inequality. And we can create complex arguments by providing multiple width@priority pairs separated by commas.

<view_string>

Used to refer to a view in the views dictionary.

<metric_string>

Used to refer to constant values in the metrics dictionary. These can be used for width and priority values.

<=
==
>=

Used to specify the relationship when setting inequalities. By default, relationships are assumed to be equal (==).

V:

Vertical layout. All descriptions that refer to width refer to height instead.



Note: Do not use any spaces in your visual format string.

Here are a few examples:

|-[view1]
Sets the standard spacing between the superview’s leading edge and view1’s leading edge.

[view1]-[view2]
Sets the standard spacing between the leading edge of view2 and the trailing edge of view1.

[view1][view2]
Aligns the leading edge of view2 to the trailing edge of view1. There will be no space between the views.

V:|-[view1]
Set’s the standard vertical spacing between the superview’s top and view1’s top.

[view(200)]
Sets view’s width to 200 points.

[view(<=200)]
Sets view’s width to 200 points or less.

[view(>= 50,<=200)]
Sets view’s width to greater than 50, but less than 200 points.

[view1(==view2)]
Sets view1’s width equal to view2's width. Note that the equal relationship is optional, however it can make the formatting string easier to read.

[view(200@500)]
Sets view’s width to 200 points, and its priority to 500.

[view(standard@high)]
Sets view’s width to the standard constant. Also sets its priority to the high constant.

We can also combine these strings into complex statements. The example below lays out three equal-sized buttons using standard system spacing between all the elements.

|-[view1]-[view2(==view1)]-[view3(==view2)]-|

We create these constraints using NSLayoutConstraint’s constraintsWithVisualFormat:options:metrics:views: method. The first argument is our visual formatting string. The second argument specifies how the elements should be aligned, and whether we should use LTR, RTL or Leading-To-Trailing relationships. Finally we pass in two dictionaries. The first defines our constants, the second our list of views.

The keys in these dictionaries must match the metric_strings and view_strings used in our visual formatting. The values must be an NSNumber (for metrics) or UIView (for views).

While we can create the view dictionary by hand, we can also use the NSDictionaryOfVariableBindings() function to automatically build one for us. To call this method, pass in our views as arguments. It returns a dictionary that uses our view’s name as the key.

This means, the following two lines of code are equivalent:

NSDictionary *views = NSDictionaryOfVariableBindings(view1, view2, view3, view4);
NSDictionary *views = @{@”view1”:view1, @”view2”:view2, @”view3”:view3, @”view4”:view4};

Note that constraintsWithVisualFormat:options:metrics:views: returns an array of constraints. We can add these constraints to a view by calling addConstraints:.

So, we can redefine our button and text field’s constraints using visual formatting, as shown below:


    NSDictionary *views =
    NSDictionaryOfVariableBindings(myTextField, myButton);
    
    // Defines both the horizontal constraints and the vertical alignment
    NSArray *constraints =
    [NSLayoutConstraint
     constraintsWithVisualFormat:@"|-[myButton]-[myTextField]-|"
     options:NSLayoutFormatAlignAllCenterY | NSLayoutFormatDirectionLeadingToTrailing
     metrics:nil
     views:views];
    
    // Define’s the button’s vertical position
    NSArray *verticalConstraints =
    [NSLayoutConstraint
     constraintsWithVisualFormat:@"V:|-[myButton]"
     options:0
     metrics:nil
     views:views];
    
    [view addConstraints:constraints];
    [view addConstraints:verticalConstraints];
    

    // Still need to set the button intrinsic size priorities
    [myButton
     setContentHuggingPriority:UILayoutPriorityRequired
     forAxis:UILayoutConstraintAxisHorizontal];
    
    [myButton
     setContentCompressionResistancePriority:UILayoutPriorityRequired
     forAxis:UILayoutConstraintAxisHorizontal];


It’s still a large chunk of code, but we’ve managed to compress five separate constraints into two visual syntax calls. Obviously, the more UI elements you have in each row or column, the greater the savings.

However, there are some limitations. For example, we cannot set a view’s width relative to its height. If we want that sort of relationship, we will need to explicitly define the constraint in code.

Interface Builder and Constraints

So far, so good. However, the real problems start cropping up when we start mixing Auto Layout and Interface Builder.

Note: we can enable Auto Layout on a storyboard-by-storyboard (or nib-by-nib) basis. Open the storyboard, and switch to the File Inspector. In the Interface Builder Document section, look for the Use Autolayout checkbox. If this is checked, the system will create Auto Layout constraints for you. Otherwise, it will layout the interface using Autoresizing masks.

Enabling Auto Layout

If any view in our view hierarchy uses Auto Layout, then the entire hierarchy will use Auto Layout. However, since the system will auto generate constraints based on our Autoresizing settings, we (hopefully) won’t notice the difference when mixing Auto Layout and Autoresizing. Also, Auto Layout is enabled by default for any new nibs or storyboards created using Xcode 4.5 or later.

When Auto Layout is enabled, Interface Builder automatically generates constraints as we drag, drop, resize and move elements in our user interface. It tries to guarantee that we have a set of constraints that unambiguously define the layout without creating any conflicts.

As you drag elements, the familiar blue guidelines now have a new meaning. With Autoresizing, they just represent the recommended spacing as defined in Apple’s HIG. With Auto Layout, they indicate the constraints that Interface Builder will create. Once you drop the interface element, these will turn into blue I-bars, visually displaying the constraints. For example, in the image below, Interface Builder generates three constraints for our button: left margin, top margin and width.

Constraints appear as blue I-bars

If you select a UI element, then hold down option and move the mouse over other views. Interface Builder will show you the size of the constraints between the selected view and the view you are hovering over in red.

Constraint values appear in red.

We can examine these constraints in both the Document Outline and the Size Inspector. We can also select a constraint and edit it’s properties in the Attribute Inspector. This allows us to change the relationship, priority, even the size. We can even create outlets for our constraints--letting us modify them or (in extreme cases) delete them programatically.

Editing a Constraints Attributes

Finally, we can add additional constraints by clicking on the Pin control.

Pin Control

This gives us a number of options. For example, we can pin our trailing edge to the superview’s right side.

Img6

As you can see, this creates an additional constraint. Since the button’s width has a lower priority than the trailing edge space, the button’s size will change as the device is rotated.

Img7

Let’s take a closer look at the Widths and Heights Equally pins. These let us create a number of views that have the same size. For example, to create a number of buttons with the same width, simply lay them out so they have the proper spacing constraints (I-bars between each of the buttons, and between the end buttons and the superview). These constraints are important. The system won’t enable Widths Equally unless they are set. Once everything’s ready, select all the buttons and pin Widths Equally. Interface Builder handles the rest.

For simple layouts, Interface Builder sets up the constraints as we expect, and everything just works. In these cases, it can be a lot easier than using Autoresizing. We don’t need to remember to go into the Size inspector to set up our Autoresizing mask. However, as the UI becomes more complex, Interface Builder may begin making incorrect or odd-seeming choices.

My biggest complaint with Interface Builder is that it often adds constraints that I don’t want--and getting rid of those constraints can be both difficult and frustrating. You’ll have to spend some time playing around with it before you reach your own level of comfort, but here are a few suggestions to help keep you sane.

Pay careful attention to the guidelines as you add, move or resize UI elements. If you want a constraint defining the space between a button and a text field, make sure you see the little blue guide line as you bring them close together. No guideline, no constraint.

Also, be aware that Interface Builder may recreate any or all its constraints whenever you make a change. This is particularly important when add your own constraints or modify the attributes of existing constraints. If you drag a new item onto the scene, Interface Builder may delete some or all of your custom constraints.

So, I recommend trying to place all your interface elements in the scene first. Then check and see how the view behaves as you rotate it. You can then start placing custom constraints or modifying the priority of constraints to fine tune the scene's behavior.

If things get too confusing, try turning Auto Layout off, then turning it back on. This usually clears all the custom constraints, and forces Interface Builder to recreate a standard set of constraints. You can then try modifying it again.

As a last resort, you can create outlets to troublesome constraints and programmatically delete them (you can also programmatically search through a view’s constraints, looking for ones to delete--but I’ll leave that as an exercise for the insane). Most of the time, this shouldn’t be necessary. Simply giving the offending constraint a very low priority should solve the problem. However, if you plan to programmatically add custom constraints (e.g. constraints that Interface Builder cannot setup for you), you may need to clean up the existing constraints to avoid conflicts.

Constraint Timing

Constraints are processed in three phases: Update Constraint, Layout and Display. The Layout and Display phases should be familiar to experienced iOS developers. Auto Layout just adds the Update Constraint phase and made some modifications to the Layout phase.

Update Constraints Phase

First, the constraints are updated. This starts with the leaf views and works up the view hierarchy towards the application’s window. If a view’s constraints need to be updated, the system will call the view’s updateConstraints method. We can override this method in our view subclasses to dynamically provide constraints at runtime.

We should never call updateConstraints directly. Instead, call setNeedsUpdateConstraints. This will mark the view for updating, and the system will call updateConstraints for us on the next pass through the Update Constraints phase.

Note: updateConstraints may not be called when a view is added to the view hierarchy. If you’re programmatically providing constraints in updateConstraints, and they don’t appear to be used, you may need to call setNeedsUpdateConstraints immediately after adding your custom view.

UIViewController has a similar method named updateViewConstraints. This method is called after its view’s updateConstraints method. We can alternatively override updateViewConstraints to programmatically provide constraints at the controller layer. updateViewConstraints is also triggered by setNeedsUpdateConstraints.

If we want to invalidate a view’s constraints, we should remove the old constraints, then call setNeedsUpdateConstraints. Our custom updateConstraints or updateViewConstraints methods can then provide the new constraints.

Layout Phase

The Layout phase proceeds in a similar manner, however it starts with the window and works down towards the leaf views. Again, if a view’s layout needs to be updated, the system will call the view’s layoutSubviews method. We indicate that a view's layout needs to be updated by calling the setNeedsLayout method.

Before Auto Layout, the default implementation of layoutSubviews did not do anything. However, we could override this method to provide our own custom layouts. Now, with Auto Layout enabled, the view will take its list of constraints and calculate the proper bound, center and frame properties for its contained views.

Note: layoutSubview’s documentation still says that the default implementation does nothing. However, the above description was implied in the WWDC 2012 presentations, and I have confirmed the timing through experimentation.

While I do not recommend mixing code that directly modifies the bounds, center and frame with Auto Layout, if you need to do this, be sure to set your custom values after the call to [super layoutSubviews]. Otherwise the system will clobber your values whenever it derives new values from the constraints (also see Constraints and Core Animation).

Display Phase

Finally, we have the Display phase. This works exactly as it did before Auto Layout. The system will call drawRect: as necessary for our views. We can trigger drawing by calling setNeedsDisplay or setNeedsDisplayInRect:.

Other Constraint Methods

There are a few other methods we can override to help control Auto Layout. These may not be as useful in our day-to-day coding, but it’s nice to know that they are there.

The first is requiresConstraintBasedLayout. This UIView class method returns NO by default. Our custom view subclasses should override this to return YES if they require Auto Layout.

Note: In my testing, I haven’t found any cases where the system’s behavior changes based on this method’s return value. If you add a view that uses Auto Layout to a view hierarchy using Autoresizing, the system simply converts the entire view hierarchy to Auto Layout. However, future code--including our own code or third-party libraries--may check this value, so we should make sure our classes behave correctly.

Next, when Auto Layout aligns two views, it actually aligns them based on their alignment rectangle--not on their frames. This might not be immediately apparent, since the alignment rectangle is set to the view’s frame by default. Still, if our view has some content that shouldn’t be considered when aligning--a badge icon that sticks out past the view’s regular content, for example, we may want to set the alignment rectangle to a subsection of its frame.

The easiest way to set the alignment rect is to override the view’s alignmentRectInsets method. This returns the UIEdgeInsets that define the offset between the alignment rectangle and the view’s frame. Alternatively, we can override alignmentRectForFrame: and frameForAlignmentRect: methods to convert between the alignment rect and its frame. By default, these methods call alignmentRectInsets, and perform the conversion based on those insets.

Finally, a view can save some or all of its constraints when it is archived. Each constraint has a shouldBeArchived property. By default, this is set to NO, and the constraint is not saved with the view. However, if we want the constraint to be saved, we can simply set its shouldBeArchived property to YES.

Troubleshooting

There are really three types of problems we can run into when creating constraints. Our constraints could be ambiguous, they could have conflicts, or they could simply be wrong.

Detecting Ambiguous Layouts

A view has an ambiguous layout if there are more than one possible layouts that satisfy all its constraints. Typically ambiguous layouts cause our view to appear in the wrong location, or to have the wrong size. Sometimes the layout may change between one run and the next, or the view may otherwise jump or shift unexpectedly.

Typically this means we need to add additional constraints. As stated earlier, each view needs at least 4 constraints to specify both its position and its size. If you are using inequalities, more constraints are needed.

However, we can also have ambiguous layout if we have conflicting non-required constraints, and if these constraints have the same priority. The system won’t be able to determine which constraint should be fulfilled. In these cases, we have to modify one of the priorities to break the tie.

Also, ambiguous constraints typically come from programmatically defined constraints. The constraints created by Interface Builder should always unambiguously describe your views. You may not like the constraints that Interface Builder chooses, but they shouldn’t be ambiguous.

Detecting and debugging ambiguous layouts is largely a matter of trial and error--however, the system does provide a few tools to help. First, we have a variety of debugging methods. These methods should only be used during debugging. For example, while writing code to explore the layout and determine why our application is not behaving properly.

UIView’s hasAmbiguousLayout is our front line tool. Quite simply, it will return YES if the view has an ambiguous layout. It will return NO otherwise. This provides a quick test. If the layout is ambiguous, we can explore further. If not, then either our constraints are just buggy, or there’s something else wrong with our code.

Next, UIView’s exerciseAmbiguityInLayout will randomly change the view’s frame to another value that also satisfies its constraints. By cycling through several alternates, you should soon get a feel for the missing constraint.

Finally, constraintsAffectingLayoutForAxis: returns an array containing all the constraints affecting the view’s layout along the specified axis. We can then examine these constraints, to get a better feel for the problem.

Remember, if you can find a good place to set a breakpoint, you can call these methods from the debugger’s console:

p (BOOL)[self hasAmbiguousLayout]

Instruments also provides some assistance. There is a Cocoa Layout instrument, that will track any changes to our constraints. The following profile shows a series of rotations. As you can see, with each rotation, the application removed then replaced a number of constraints on the Window.

Cocoa Layout Instrument

Detecting Conflicts

Conflicts are both more serious and easier to detect. Conflicts occur when two (or more) required constraints cannot both be satisfied. Fortunately, the system throws an exception when it detects conflicting constraints.

By default, the system also catches this exception. It logs a warning to the console, then choses one of the constraints to ignore. It will then continue to layout your views.

A sample warning is shown below:

2012-12-28 01:07:09.880 AutoLayoutTest[43733:c07] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
  "<NSLayoutConstraint:0x7581490 H:[MyView:0x7580c30(60)]>",
  "<NSLayoutConstraint:0x7581320 H:[MyView:0x7580c30(80)]>"
)

Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7581320 H:[MyView:0x7580c30(80)]>


Note: When possible NSLayoutConstraint’s description method uses visual formatting. Where this is not possible, it will use pseudo code to write out the linear equation directly.

In this example, you can see that we’ve set two different constraints specifying the height of this MyView instance. In one case, we set the height to 60 points. In the other, we set it to 80 points. The system then choses to ignore the 80-point constraint and continue to layout the view.

We can set a breakpoint to detect this exception. Either set an exception breakpoint or set a symbolic breakpoint for objc_exception_throw. However, this will only provide useful information for constraints set in a view’s updateConstraints method. Constraints added anywhere else will throw an exception during the next update phase--not at the point when the constraint was added.

Constraints and Core Animation

Before Auto Layout, if we wanted to animate a change in position, we could just modify the view’s frame or center property inside an animation block, as shown below:

    CGRect frame = self.subview.frame;
    
    CGFloat top = CGRectGetMinY(frame);
    CGFloat left = CGRectGetMinX(frame);
    
    if (left < CGRectGetWidth(self.view.bounds)) {
        left += 100.0;
    } else {
        left = 0.0;
        top += 100.0;
    }
    
    
    frame.origin.x = left;
    frame.origin.y = top;
    
    [UIView animateWithDuration:0.25 animations:^{

        self.subview.frame = frame;
        
    }];


While this will still move the view, it doesn’t change the constraints. This means the constraints will clobber your new settings during the next layout phase. This may occur immediately after the animation block--or it may occur when the view is rotated, or when some other change is made to the view hierarchy.

To properly animate any changes to a view’s size or position, we need to move away from using the bounds, frame and center properties, and begin modifying the constraints themselves. There are really 3 steps: remove the old constraints, add the new constraints, then call layoutIfNeeded inside an animation block.

Unfortunately, the new code is quite a bit more complex than the old code--and these were a relatively simple set of constraints.

    UIView *view = self.view;
    
    CGFloat top = self.topMargin.constant;
    CGFloat left = self.leftMargin.constant;
    
    // Remove Old Constraints
    
    [view removeConstraint:self.topMargin];
    [view removeConstraint:self.leftMargin];
    
    if (left < CGRectGetWidth(view.bounds)) {
        left += 100.0;
    } else {
        left = 0.0;
        top += 100.0;
    }
    
    // Add New Constraints
    
    self.topMargin =
    [NSLayoutConstraint constraintWithItem:self.subview
                                 attribute:NSLayoutAttributeTop
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:view
                                 attribute:NSLayoutAttributeTop
                                multiplier:1.0
                                  constant:top];
    
    self.leftMargin =
    [NSLayoutConstraint constraintWithItem:self.subview
                                 attribute:NSLayoutAttributeLeading
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:view
                                 attribute:NSLayoutAttributeLeading
                                multiplier:1.0
                                  constant:left];
    
    [view addConstraint:self.topMargin];
    [view addConstraint:self.leftMargin];
    
    // Call layoutIfNeeded
    
    [UIView animateWithDuration:0.25 animations:^{
        [view layoutIfNeeded];
    }];


We can also modify existing constraints; however, this has limitations. We can only modify the constraint’s constant property. So the following code would move the subview:

    self.topMargin.constant = top;
    self.leftMargin.constant = left;


Unfortunately, we cannot animate these changes in iOS (OS X could animate it using the animator proxy). If you are feeling really determined, you could produce an animation effect by using an NSTimer to make small incremental changes to these values. However, at that point, it’s probably easier to just remove and replace the whole constraint.

Conclusion

As you can see, Auto Layout gives us considerably more power when it comes to laying out our view hierarchy--but this power comes at a cost. No question, Auto Layout is more complex than Autoresizing, and I often wonder if the system isn’t a bit over-engineered. Yes, it solves a number of really complex problems, but do these solutions pay for the additional complexity?

For example, it can greatly simplify internationalization, since buttons can now automatically adapt their size based on the length of their titles. A well-designed interface should be able to automatically reconfigure itself as you move from one language to another. This avoids the need for separate, language specific layouts.

Still, I’m not sure how well this advantage will play out on the more space-constrained iOS screens. I imagine a number of applications may choose to implement separate layouts for each language, just because it gives them fine control over a highly limited resource.

Having said that, many of the problems people are having with Auto Layout undoubtedly come from their unfamiliarity with it. It is a radical paradigm shift away from the older frame/bounds/center layouts. And, as with all paradigm shifts, it may take some time to get used to.

Auto Layout quickly becomes more useful as you grow more familiar with it. Yes, it can be daunting when you first start out. However, once you get the feel for it, it really isn’t that bad.

If you wish to explore this topic in more detail, I recommend watching the videos from WWDC 2012. There are three videos on Auto Layout: Introduction to Auto Layout for iOS and OS X, Best Practices for Mastering Auto Layout and Auto Layout by Example.

My best recommendation, however, is to just start playing around with it. Build some simple exploratory projects. Play with the constraints that Interface Builder creates, then try to programmatically build your own. The only way to really get comfortable is to get some digital dirt under your fingernails. So, go. Play. And try to have fun.

Friday
Oct052012

FMSAlertManager added to GitHub

Ok, I've tried to create my first GitHub repository. I think I did something wrong, however. It lists the project as Javascript, not Objective-C. If you know how to fix this--or if you see anything else I screwed up--please let me know.

Still, this is an updated version of the block-based UIAlertView API I described in this post.

After using the code in my own projects, I made a number of small improvements. For example, it can now auto-close alert views when the app goes into the background, and you can get access to the alert view instance so you can programatically press buttons or dismiss it. I've also created Docset documentation.

Check it out here: https://github.com/rikiwarren/FMSAlertManager.

Monday
Sep242012

Advanced Storyboard Techniques

I'm not afraid to admit it. I love Storyboards. They were one of the new features in iOS 5 that greatly changed how I develop applications. I must say, it's nice to see how they continue to evolve and mature in iOS 6.

Not surprisingly, I heavily emphasized Storyboards in my book, Creating iOS 5 Apps: Develop and Design. However, it turns out they are a lot more flexible and powerful than I originally gave them credit for. So, without further ado, here's a shotgun blast of advanced Storyboard tips and techniques. Some of it applies to iOS 5; however, much of it is new for iOS 6. 

Segue Timing

Understanding the timing behind segues is important to using them successfully. When a new segue fires, the following steps occur:

  1. The destination view controller is instantiated (initWithCoder: is called)
  2. The source view controller's prepareForSegue:sender: method is called
  3. The destination view is loaded
    1. The destination's viewDidLoad is called
  4. The destination view appears
    1. The source's viewWillDisappear: is called
    2. The destination's viewWillAppear: is called
    3. The source's viewDidDisappear: is called
    4. The destination's viewDidAppear: is called.

When a view controller is disposed (dismissing a modal view or popping a navigation view), we go through the following sequence:

  1. The source's viewWillDisappear: is called
    1. The destination's viewWillDisappear: is called
    2. The source's viewWillAppear: is called
    3. The destination's viewDidDisappear: is called
    4. The source's viewDidAppear: is called.
  2. The destination view is deallocated

There are a few key point worth noting. Every time we fire off a segue, we will instantiate (and then later deallocate) a new copy of the destination's view controller. If you need to preload or cache your view controllers, you will want to manage the view controllers programmatically.

Also, when the prepareForSegue:sender: method is called, the destination controller's view has not yet loaded. You cannot access any of that controller's outlets. The best practice is to just pass whatever data you wish to the destination controller--storing the data in properties as necessary, and then let the destination use its properties in its viewDidLoad method to configure its own outlets.

Finally, our code can alter the order--sometimes in unexpected ways. For example, if we touch the destination's view property before its view has loaded, we will force the view to load. This can cause the destination controller's viewDidLoad method to fire earlier than we expected. Just another reason to leave the view (and everything inside the view) alone. 

Loading New Views Programmatically

Let's say we want to draw a segue to a view controller that builds its views programatically (by overriding its loadView method). Originally, I thought this was not possible; that we would have to manage the transition to this view controller entirely in code. Turns out, we can have the Storyboard automatically instantiate and deallocate our view controller--even though we're not designing its view in the Storyboard. There's just one small trick to make it all work:

  1. Make a UIViewController subclass.
  2. Implement its loadView method.
  3. In the Storyboard, drag out a view controller as normal.
  4. Set the view controller's class to your subclass.
  5. Set the segue to new view controller.
  6. Now, comes the secret sauce--delete the view from the view controller's scene.

ProgramaticViewController

When the segue fires, the view will be created using the slightly modified sequence:

  1. The destination view controller is instantiated (initWithCoder: is called)
  2. The source view controller's prepareForSegue:sender: method is called
  3. The destination view is loaded
    1. The destination's custom loadView method is called
    2. The destination's viewDidLoad method is called
  4. The destination view appears
    1. The source's viewWillDisappear: is called
    2. The destination's viewWillAppear: is called
    3. The source's viewDidDisappear: is called
    4. The destination's viewDidAppear: is called.

We can then instantiate and layout our views and subviews in the loadView method, as normal. We can even draw segues from this view controller to other view controllers in the Storyboard--though, since we don't have any controls to link the segue to, we will need to trigger these segues in code (draw the segue from the controller itself, then have the controller call performSegueWithIdentifier:sender: to trigger the desired segue).

Loading New Views Using Nibs

Being able to include programatically-designed view controllers is cool--but it would be even better if we could load the view controller from another nib file. This is basically the same as the previous example, except instead of implementing the loadView method, we provide a properly named nib file. 

Note: There's no way to specify the nib file for our view controller. However, whenever no name is provided, UIViewController searches for a nib whose name matches the view controller's class name. So, if we have a view controller called MyCoolViewController, it would look for MyCoolViewController.xib. As long as the names match, the nib will load as expected.

Loading New Views From Other Storyboards

For me, this is the holy grail for Storyboards. I'd love to be able to create a Storyboard that is a composite of other Storyboards. Ironically, there doesn't appear to be any way to do this from within Interface Builder. The "add the view controller, then delete the view" trick won't work. Don't get me wrong, we can still string together Storyboards, but we have to stitch everything together in code.

To segue to another Storyboard:

  1. Get the Storyboard by calling UIStoryboard's storyboardWithName:bundle: method.
  2. Get the view controller by calling instantiateInitialViewController (to get the initial view controller) or instantiateViewControllerWithIdentifier: (to get an arbitrary controller from within the Storyboard).
  3. Present the view controller (e.g. presenting it modally or pushing it onto a navigation controller).

It's a bit annoying that we have to do all this in code--but it's still a nice trick to master. There are often good reasons to split a Storyboard into separate, independent modules. For example, you might have different people working on different parts of the UI. Or, you might just want to prevent a single Storyboard from becoming overly complex.

On the other hand, many of the benefits of using Storyboards (e.g. drawing segues or unwinding segues between scenes) won't work between Storyboards. So, there are also good reasons to keep everything together. I guess the bottom line is, don't split things up just for the sake of splitting things up. But, don't be afraid to split them up either.

Unwinding Segues

In iOS 5, we could easily pass data forward along the segues--but we didn't have an automated way of passing data back. To get around this, we typically used a delegate pattern. The destination view would declare a delegate protocol. The sending view would then implement this protocol. In the prepareforSegue:sender: method, the sender would assign itself as the destination's delegate. The destination controller could then call any of the delegate methods--allowing us to send arbitrary messages back upstream.

While this is still a powerful design pattern, iOS 6 has partially automated the procedure--letting us more easily create a wider range transitions. Basically, we can define how the Storyboard will unwind any given segue.

In the controller you wish to return to, implement a method that returns an IBAction, and that takes a single UIStoryboardSegue argument:

- (IBAction)returnActionForSegue:(UIStoryboardSegue *)returnSegue {

// do useful actions here.

}

Now, in the scene we wish to unwind, control-drag from the UI element that will trigger the return to the scene's exit icon (the green icon in the dock). In the popup menu, select the previously defined return item.

DragToExitIcon

Note1: We cannot simply create a segue from the button back to the desired scene, since that will instantiate an entire new copy of the scene. Instead, we want to return to the existing copy of the scene.

Note2: We don't necessarily need to return to the previous scene. This is particularly handy if we are pushing a number of scenes into a navigation controller, and want to pop back to an arbitrary point in the middle--or if we're mixing modal and push segues, and want to unwind to any point in the chain.

The Details

The system will search for the return destination by calling canPerformUnwindSegueAction:fromViewController:withSender: on the current view controller. By default, this method simply checks to see if you have a method whose selector matches the return action. If it does not find a match, it will search the parent view controller, and will continue searching up the chain of parent view controllers until it finds a match.

We can override canPerformUnwindSegueAction:fromViewController:withSender: to add more control to the search--for example, if we define the same return action in multiple view controllers, and then override this method to make sure we return to the correct one.

Furthermore, container view controllers are responsible for searching their child view controllers. Apple's built-in container views all do the right thing. For example, a UINavigationViewController will search through the view controller stack from the top down, starting just below the exiting controller.

If we want our own custom container controller to search through its child view controllers, we need to override viewControllerForUnwindSegueAction:fromViewController:withSender:. In this method, we should iterate over our child view containers, calling canPerformUnwind… on them. If we find a match, we should return it. If not, we should call the superclass's implementation and return the result.

Once it finds a match, the system then calls segueForUnwindingToViewController:fromViewController:identifier: on the destination controller. Again, we need to override this method for our custom container controllers, returning a segue that performs the animation and other necessary steps to unwind the view controllers.

Then, the system calls prepareForSegue:sender: on the exiting view controller, followed by calling the exit action on the destination view controller. We can use either of these steps to pass data back to the return point.  Finally the system triggers the segue.

This is a bit complicated, so let's walk through a concrete example. Let's say I have a multi-page form that I want users to fill out. To use the form, I modally present a navigation controller. The navigation controller's root view controller is the first page of my form. As the user fills out each page, I push a new one on the navigation controller. On the final page I have a done and a clear buttons. 

The done button should take the user back to my original view (the navigation controller's presenting view controller). The clear button should just take them back to the beginning of the form.

To set this up, I need to implement two methods. formCompleted: should be implemented on the view controller that will present the form, while clearForm:  should be implemented on the first page's view controller. I then draw connections from the done button to the formCompleted:  exit action, and from the clear button to the clearForm:  exit action.

When the clear button is pressed, the system searches the final page's view controller for the clearForm:  method. It doesn't find a match, so it searches the parent view controller--our navigation controller. The navigation view controller manages the search of its children. Basically, it starts at the top of the stack, just below the last page controller, and works its way down.  Eventually it finds the method on its root view controller--so it returns that controller as the destination.

The navigation controller then creates a segue where it pops to the destination with animation, and passes this back to the final page controller. The final page's prepareForSegue:sender: is called. The root controller's clearForm: method is called. Then the segue is triggered.

If I press the done button, it will search the last page, then search the navigation controller. This time, the navigation controller doesn't find anything, so the system will search its presenting controller. Here a match is found. The presenting controller automatically creates a segue to dismiss the modal view controller with the correct animation. Again, the final page's prepareForSegue:sender: is called. The root controller's formComplete:  method is called, and the segue is triggered.

As you can see, the ability to unwind segues allows us to easily return to any existing view controller in our view controller hierarchy. in iOS 5, these transitions required a considerable amount of custom code--the more complex the transition, the more complicated the code. Now, these transitions can largely be automated, saving us time and effort, and hopefully avoiding bugs.

Container Views

The iOS SDK has always had two different types of view controllers. The most basic type are the content view controllers. These are the view controllers that we create to display our content. However, the system also had a number of pre-build container view controllers: UINavigationControllerUITabBarController and others. These controllers usually provided a minimal user interface of their own (the navigation or tab bar itself). Instead, they manage a number of content view controllers for us.

With iOS 5, Apple gave us the ability to create our own content view controllers. This is a surprisingly powerful technique, letting us easily break a complex user interface into several independent parts. However, all of the management of these sub view controllers and their views had to be done in code.

Now, with iOS 6, we can add a container view to our Storyboards (though, not our nibs). Simply drag a Container View object from the library and place it on one of your scenes. Interface Builder will automatically add a content view controller for the container view, with an embed segue--though you can change the segue to embed any view controller in the Storyboard.

AddingAContainerView

When the parent view controller is loaded, this segue will fire, and the child view will also load. Timing-wise, the child view will finish loading before the parent's viewDidLoad method is called, so you can safely configure the child from within the parent's viewDidLoad.

Unfortunately, this only gets us about half way to our goal. Don't get me wrong. It's a cool new feature, and I love the fact that I can graphically lay out my container views. However, I typically use container views because I'm going to be dynamically swapping the content within those views. And this may or may not be possible to set up in the Storyboard.

If the child views are responsible for swapping themselves around, then you can simply draw segues from one child to the next as usual. There are no problems, and everything just works (though, see the note on unwinding segues above).

However, there's no way to have the parent view controller drive these segues. To really let the parent freely drive arbitrary transitions between all the possible child controllers, you'll have to manage everything in code. Here's a common work flow:

  1. Instantiate the new child view controller.
  2. Set the frame of the new child controller's view to the existing container view.
  3. Animate the transition between the views.
  4. Remove the old child controller from the parent.
  5. Add the new child controller to the parent.

Assuming you only have a single container view--and thus only a single child controller--you could implement this as shown below. Obviously, if you want to preload or cache the view controllers, or if you have multiple container views, your implementation may become much more complex. 

// Assumes we have a MyChildViewController.xib, or that 
// MyChildViewController programmatically creates its views.
MyChildViewController *controller = [[MyChildViewController alloc] init];

// Assumes we only have 1 child view controller
UIViewController *currentChild = [self.childViewControllers lastObject];
controller.view.frame = self.containerView.frame;

[UIView
 transitionFromView:self.containerView
 toView:controller.view
 duration:0.25f
 options:UIViewAnimationOptionTransitionCrossDissolve
 completion:^(BOOL finished) {
    [currentChild removeFromParentViewController];
    [self addChildViewController:controller];
}];

 

If you want to load another controller from within the Storyboard, you would modify the code as shown below:


UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];

// Must match the controller's Storyboard ID
SecondChildViewController *controller =
[storyboard instantiateViewControllerWithIdentifier:@"SecondChild"];


// Assumes we only have 1 child view controller
UIViewController *currentChild = [self.childViewControllers lastObject];
controller.view.frame = self.containerView.frame;

[UIView
 transitionFromView:self.containerView
 toView:controller.view
 duration:0.25f
 options:UIViewAnimationOptionTransitionCrossDissolve
 completion:^(BOOL finished) {
    [currentChild removeFromParentViewController];
    [self addChildViewController:controller];
}];

 

Be sure to set the matching Storyboard ID in the Identity Inspector for the child view controller--otherwise this won't work.

And that's it for now. I hope you found at least some useful information in here. Also, please let me know if you find any other cool Storyboard tricks.