Search
Rich's Mad Rants
Powered by Squarespace
Tuesday
Jun252013

Updating Book for iOS 7

 

Hey all,

I'm in the process of updating my book for iOS 7. There's a ton of stuff I'd like to talk about--and I'm not sure I'll be able to fit everything in. OK, I'm sure that I won't be able to fit everything in. Which means I have a lot of hard decisions to make.

So, I'm throwing the question out there. What would you like to see in an iOS 7 book? Are there any other changes to my book, specifically, that you'd like to see.

Feel free to leave suggestions in the comments--but remember, much of iOS 7 is still under NDA. Basically, if it wasn't mentioned during the WWDC keynote, we cannot talk about it.

If you want to talk to me about NDAed stuff, please send your comments to me via email.

Thanks. I greatly appreciate any feedback and suggestions.

-Rich-

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).

Tuesday
Jan152013

Looking Back on CES

Last week, I had the opportunity to speak at CES. I was honored to be included in the MacTech Insight track, where I co-presented a session called "The Cloud, Apple Style" with Andy Espo. All things considered, the presentation went very well (I'll post a link to the slides when they become available), and I had a good time--despite the exhaustion. Most importantly, I didn't get sick.

It was my first time at CES, and given all the bad press the conference has received this year, I thought I'd briefly write up my impressions.

It's Not Your Conference

No matter who you are, no matter how broad your interests lie, large parts of the conference will simply not be for you. In part, this is a symptom of the number of products covered at CES. For example, I spent twenty minutes wandering past displays of different car audio systems, before I decided that I just didn't give a crap. Same thing with all the photography equipment. Don't get me wrong. I like photography (which is more than I can say for cars). I used to develop my own black and white film. But, for a long time now, my camera of choice has been my phone. I just don't know enough to even be impressed.

But it's not just about the subject matter, it's also about the intended audience. A large number of people come to CES for a wide range of reasons. Much of the real action happens out of sight. It is probably the only time you get so many component manufacturers, distributors, buyers, company executives, press and more all in one spot. This means, much of what you see won't make sense, because it's not aimed at you. For example, I was walking along a row of iPhone cases, when I stumbled across a booth displaying cables. All kinds of cables. Some 1.5 inches in diameter. Others tiny. Undoubtedly, someone at the convention was looking for a good cable manufacturer--but to me, it just seemed somewhat odd.

There is also another, more frustrating side to this. I kept an eye on the news coming out of CES, in a misguided attempt to see everything. I soon discovered that many of the products described in the tech press were simply not available to the public. You could not see them (much less touch them) unless you had an invitation.

Our Unevenly Distributed Future

Let's just say, with that many companies displaying their products, you're bound to find a wide range of build qualities and technical achievement. But, sometimes it was just sad. When walking through the health tech area, I would often see one booth with giant fist-sized health monitoring equipment--most built with a vintage 90's aesthetic. Then, just two booths down, I'd find the same exact tool, only a newer, slicker version that could connect with my smart phone or tablet. Something that could display stunning visualizations of the data it collected. 

And it wasn't just the health sector--it was everywhere. At first I thought it was funny, but I soon felt sad for the growing number of gadgets that had clearly been left behind.

Duplicates and Derivatives Everywhere

As you walk around, you see the same thing over and over again. Everyone had their 4K TV (or Ultra HD, as the kids say). There were hundreds of iPhone case makers. And everyone's getting into the smart watch game. The sad thing is, there's a lot more duplication and copying than innovation--and that can become tiresome.

I was particularly interested in both the quantified self/health tracker market, so I spent a good deal of time walking through that part of the show floor and talking to people. If you want a low-power bluetooth heart rate strap, or a fitbit-like tracker, you have a large number of options. Even Withings slick new Smart Activity Tracker is really just a fitbit knockoff. Say what you like about the Happi fork, but at least they are building something unique.

There were even 3 different 3D printer manufacturers at the show--or at least, three that I found. And that's a technology that's barely out of the garage.

Technologies not Products

Surprisingly, some of my favorite booths weren't about products, but technologies. Some of these were startups filled with bright-eyed, eager employees, showing off demos of their baby. Others were established technology companies, come to strut their stuff.  For example, Murata's self-balancing, bicycling robot was never intended to be a product--but as a demo of their components, it was both impressive and fun.

On the other extreme, one of my favorite demos was the Tobii Gaze. They used eye tracking to let me control the mouse, scrolling and zoom points just by glancing around the screen. I'll try to write more about it (and the other cool things that I saw), but suffice it to say, I was blown away by both the utility and the accuracy. We may not have built-in gaze tracking in any of devices this year. Probably not for a couple of years. But, if what I saw was any indication, a whole slew of alternate input technologies are on their way.

%#&$ Gagnam Style

The first time I saw a robot dancing to Psy's Gagnam Style, I thought it was clever and funny. The fifth time, I was ready to break the little droid's scrawny, plastic neck. I guess this is just another example of duplication. If only one company had thought of it, it would have been a great idea. Unfortunately, everyone had the same "great idea."

But Wait, There's More

No matter how far I walked, or how many booths I visited, I always felt like I was missing something. And there's a good reason for that. I was missing things--a lot of things. 

It's hard to really understand how big the convention is. It's too big to fit in one venue. Every time I thought I had a handle on it, I discovered that there was more. Another floor that I hadn't known about. Another venue, sometimes up to a mile away from the convention center. And even the areas that I walked through several times, each time I passed through, I found something new. Something that I had glazed over on all my earlier trips.

You cannot possibly see it all. It is literally unbelievable. The mind simply cannot hold all of CES. And you probably shouldn't even try. Pick the things you're interested in, and make sure you see those. Save some time for exploring--you never know what you'll find. And compare notes at the end of the day--both with your friends and with the numerous blogs and news outlets covering the event. They'll give you a whole new list of must-see items for the next day.

I am very glad that I had a chance to go to CES. Would I go again? If I'm offered another chance to speak, definitely. But, would I go just to see all the glorious gadgets--I'm not so sure. My time and money would undoubtedly be better off spent going to one of the many iOS Developers conferences. They're more directly related to my day-to-day work. On the other hand, if I was doing pure tech journalism, you wouldn't be able to keep me away.

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.

Wednesday
Oct102012

FMSSwizzler library added to GitHub

Hey all.

At the risk of repeating myself, I'll be speaking at the MacTech Conference next week in L.A. I'm actually doing two different presentations. One is on iCloud Document Syncing, the other on Metaprogramming in iOS. In my head, I'm king of thinking them as the "useful" talk and the "fun" talk.

As part of my prep, I've been putting together some examples. Here is another library of useful methods for Objective-C metaprogramming. Specifically, I have methods to simplify method- and class-swizzling. Also, unlike traditional Objective-C metaprogramming--I'm using the block-based API for dynamically adding methods to classes.

I've tried to address all the obvious gotchas (class clusters, KVO, nested dymanic subclasses, etc.), but--obviously--mucking around with classes at runtime will always be a bit dangerous. There are probably a few edge cases that I haven't though of. Take a look at the included documentation and the unit tests, if you have any questions. Most importantly, if you use the code in your own projects (not just when doing exploration coding or debugging), be sure to use it judiciously and test it ferociously.

You can find the repository at https://github.com/rikiwarren/FMSSwizzler. And, you can get more informaiton about MacTech Conf here.

Page 1 ... 2 3 4 5 6 ... 21 Next 5 Entries »