Chapter 7: Great at Any Angle: Collection Views and Auto Layout

Until now (before iOS 6), developers used UITableView for almost any type of display that shows a collection of items. Though Apple has been using a UI that looks similar to the collection views in photo apps for quite a long time (since the original iPhone), that UI wasn’t available for use by third-party developers. We depended on third-party frameworks like three20, or we hacked our own to display a collection list. For the most part they worked well, but implementing animation when items are added or deleted or implementing a custom layout like cover flow was difficult. Fret not. iOS 6 introduces a brand new controller for iOS just for displaying a collection of items. Collection view controllers are a completely new building block UI that’s similar to table view controllers. In this chapter, I introduce you to collection views in general, and you find out how to display a list of items using a collection view.

Later in this chapter, I introduce you to one of Cocoa’s features, Auto Layout. Auto Layout debuted for the Mac last year with Mac OS 10.7 SDK. Apple is introducing Auto Layout for iOS apps with iOS 6. In this chapter, you discover Auto Layout, focusing specifically on the layout options that are impossible to do now without writing code. I assume that you have experience customizing table views. If not, read Chapter 6 and then come back to this chapter. I also assume that you know the basics of storyboards and segues. If you don’t, see Chapter 21 of this book. You don’t need to know about complex custom transition effects with storyboards, just a basic understanding will do.

After you complete this chapter, you’ll be proficient with two new UI concepts that will help you push the limits on your apps and take them to the next level. With that, it’s time to get started.

Collection Views

iOS 6 introduces a new controller, UICollectionViewController. Collection views provide a much more elegant way to display items in a grid than was previously possible using UIKit. Collection views were available on the Mac OS X SDK (NSCollectionView); however the iOS 6 collection view (UICollectionView) is very different from the Mac equivalent. In fact, iOS 6’s UICollectionViewController/UICollectionView is more similar to UITableViewController/UITableView, and if you know how to use UITableViewController, you’ll be at home with UICollectionViewController.

In this chapter, I explain the different classes that you need to know about, and I walk you though an app that displays a directory of images in a collection view. As I walk you through this, I’ll be comparing UICollectionViewController with the UITableViewController that you know well by now (because learning by association helps us better remember).

Classes and Protocols

In this section I describe the most important classes and protocols that you need to know when you implement a collection view.

UICollectionViewController

The first and most important class is the UICollectionViewController. This class functions similarly to UITableViewController. It manages the collection view, stores the data required, and handles the data source and delegate protocol.

UICollectionViewCell

This is similar to the good old UITableViewCell. You normally don’t have to create a UICollectionViewCell. You’ll be calling the dequeueCellWithReuseIdentifier:indexPath: method to get one from the collection view. Collection views behave like table views inside a storyboard. You create UICollectionViewCell types (like prototype table view cells) in Interface Builder (IB) inside the UICollectionView object instead. Every UICollectionViewCell is expected to have a CellIdentifier, failing which you’ll get a compiler warning. The UICollectionViewController uses this CellIdentifier to enqueue and dequeue cells. The UICollectionViewCell is also responsible for maintaining and updating itself for selected and highlighted states. I show you how to do this later in this chapter.

The following code snippet shows how to get a UICollectionViewCell:

MKPhotoCell *cell = (MKPhotoCell*)

[collectionView dequeueReusableCellWithReuseIdentifier:

@”MKPhotoCell” forIndexPath:indexPath];

The code is straight from the sample code for this chapter that I walk you through later.

If you’re not using Interface Builder or storyboards, you can call the registerClass:forCellWithReuseIdentifier: in collectionView to register a nib file.

UICollectionViewDataSource

You’ve probably already deduced by now that this should be similar to UITableViewDataSource. Yes, you’re right. The data source protocol has methods that should be implemented by the UICollectionViewController’s subclass. In the first example, you will implement some of the methods in the data source to display photos in the collection view.

UICollectionViewDelegate

The delegate protocol has methods that should be implemented if you want to handle selection or highlighting events in your collection views. In addition, the UICollectionViewCell can also show context-sensitive menus like the Cut/Copy/Paste menu. The action handlers for these methods are also passed to the delegate.

Example

You start by creating a single view application in Xcode. In the second panel, select Use Storyboards and Use Automatic Reference Counting, Choose iPad as the target device and click Next, and choose a location to save your project.

Editing the Storyboard

Open the MainStoryboard.storyboard file and delete the only ViewController inside it. Drag a UICollectionViewController from the object library. Ensure that this controller is set as the initial view controller for the storyboard.

Now, open the only view controller’s header file in your project. Change the base class from UIViewController to UICollectionViewController and implement the protocols UICollectionViewDataSource and UICollectionViewDelegate. Go back to the storyboard and change the class type of the collection view controller to MKViewController (or whatever, depending on your class prefix).

Build and run your app. If you followed the steps properly, you’ll see a black screen in your iOS simulator.

We could have used a blank application template and added a storyboard with a collection view as well. But that requires changes to App Delegate and your Info.plist because the Xcode’s blank application template doesn’t use storyboards.

Adding Your First Collection View Cell

Well, the application’s output wasn’t impressive, right? Lets’ spice it up. Go ahead and add a class that is a subclass of UICollectionViewCell. In the sample code, I’m calling it MKPhotoCell. Open your storyboard and select the only collection view cell inside your collection view controller. Change the class to MKPhotoCell. This is shown in Figure 7-1.

Open the Attributes Inspector (the fourth tab) in the Utilities and set the Indentifier to MKPhotoCell. This step is very important. You will use this identifier to dequeue cells later in code. Add a blank UIView as the subview of your collection view cell and change the background color to red (so you can see it).

9781118449974-fg0701.tif

Figure 7-1 Xcode showing a storyboard with a collection view and a prototype collection view cell

Implementing Your Data Source

Now implement the data source as shown here:

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)

  collectionView {

  

  return 1;

}

- (NSInteger)collectionView:(UICollectionView *)

collectionView numberOfItemsInSection:(NSInteger)section {

  

  return 100;

}

// The cell that is returned must be retrieved from a call to

// -dequeueReusableCellWithReuseIdentifier:forIndexPath:

- (UICollectionViewCell *)collectionView:(UICollectionView *)

   collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

  

  MKPhotoCell *cell = (MKPhotoCell*)

   [collectionView dequeueReusableCellWithReuseIdentifier:@”MKPhotoCell”

   forIndexPath:indexPath];

  return cell;

}

Now, build and run the app. You’ll see a grid of 100 cells, each painted in red. Impressive. All it took was a few lines of code. What is even more impressive is that the grid rotates and aligns itself when you turn your iPad to landscape.

Using the Sample Photos

You’ll now replace the red-colored subview with something more interesting. You’re going to display photos from a directory. Copy some photos (about 50) to your project. You can use the sample photos from the example code.

Remove the red-colored subview you added in the previous section and add a UIImageView and a UILabel to the UICollectionViewCell. Add outlets in your UICollectionViewCell subclass and connect them appropriately in Interface Builder.

Preparing Your Data Source

Prepare your data source by iterating through the files in your directory. Add this to your viewDidLoad method in the collection view controller subclass. You can find it in the MKViewController class in the example code.

  self.photosList = [[NSFileManager defaultManager]

   contentsOfDirectoryAtPath:[self photosDirectory] error:nil];

The photosDirectory method is defined as

-(NSString*) photosDirectory {

  return [[[NSBundle mainBundle] resourcePath]

stringByAppendingPathComponent:@”Photos”];

}

Now, update your data source methods to return data based on this information. Your previous code was returning 1 section and 100 items in that section. Change the value 100 to the number of photos you added to your project in the previous section. This is the size of the photosList array.

UICollectionViewDataSource Methods (MKViewController.m)

- (UICollectionViewCell *)collectionView:(UICollectionView *)

   collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

  

  MKPhotoCell *cell = (MKPhotoCell*) [collectionView

  dequeueReusableCellWithReuseIdentifier:@”MKPhotoCell”

  forIndexPath:indexPath];

  

  NSString *photoName = [self.photosList objectAtIndex:indexPath.row];

  NSString *photoFilePath = [[self photosDirectory]

   stringByAppendingPathComponent:photoName];

  cell.nameLabel.text =[photoName stringByDeletingPathExtension];

  UIImage *image = [UIImage imageWithContentsOfFile:photoFilePath];

  UIGraphicsBeginImageContext(CGSizeMake(128.0f, 128.0f));

  [image drawInRect:CGRectMake(0, 0, 128.0f, 128.0f)];

  cell.photoView.image = UIGraphicsGetImageFromCurrentImageContext();

  UIGraphicsEndImageContext();

  return cell;

}

Build and run the app now. You’ll see the photos neatly organized in rows and columns. Note, however, that the app creates a UIImage from a file in the collectionView:cellForItemAtIndexPath: method. This is going to hurt performance.

Improving Performance

You can improve the performance by using a background GCD queue to create the images and optionally cache them for performance. Both these methods are implemented in the sample code. For an in-depth understanding on GCD, read Chapter 13 in this book.

Supporting Landscape and Portrait Photos

The previous example was good, but not great. Both portrait and landscape images were cropped to 128×128 and looked a bit pixelated. The next task is to create two cells, one for landscape images and another for portrait images. Because both portrait and landscape images differ only by the image orientation, you don’t need an additional UICollectionViewCell subclass.

Create another UICollectionViewCell in your storyboard and change the class to MKPhotoCell. Change the image view’s size so that the orientation is portrait and the older cells’ orientation is landscape. You can use 180×120 for the landscape cell and 120×180 for the portrait cell. Change the CellIdentifier to something like MKPhotoCellLandscape and MKPhotoCellPortrait. You’ll be dequeuing one of these based on the image size.

When you’re done, your storyboard should look like Figure 7-2.

9781118449974-fg0702.tif

Figure 7-2 Storyboard showing two cells, one for landscape images and another for portrait images

Determining the Orientation

You now have to determine which cell to dequeue based on the image’s orientation. You can get the image orientation by inspecting the size property of the UIImage after you create it. If the image is wider than its height, it’s a landscape photo; if not it’s in portrait orientation. Of course, if you’re going to compute this in your collectionView:cellForItemAtIndexPath: method, your scrolling speed will be affected. The sample code precomputes the size and hence the orientation and stores the orientation in a separate array when the view loads, all on a background GCD queue. The following code listing shows the modified collectionView:cellForItemAtIndexPath:.

collectionView:cellForItemAtIndexPath: Method in MKViewController.m

- (UICollectionViewCell *)collectionView:(UICollectionView *)

   collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

  

  static NSString *CellIdentifierLandscape = @”MKPhotoCellLandscape”;

  static NSString *CellIdentifierPortrait = @”MKPhotoCellPortrait”;

  

  int orientation = [[self.photoOrientation objectAtIndex:

   indexPath.row] integerValue];

  

  MKPhotoCell *cell = (MKPhotoCell*)

   [collectionView dequeueReusableCellWithReuseIdentifier:

                            orientation == PhotoOrientationLandscape ?

                            CellIdentifierLandscape:CellIdentifierPortrait

                            forIndexPath:indexPath];

  

  NSString *photoName = [self.photosList objectAtIndex:indexPath.row];

  NSString *photoFilePath = [[self photosDirectory]

    stringByAppendingPathComponent:photoName];

  cell.nameLabel.text =[photoName stringByDeletingPathExtension];

  

  __block UIImage* thumbImage = [self.photosCache objectForKey:photoName];

  cell.photoView.image = thumbImage;

  

  if(!thumbImage) {

    dispatch_async(dispatch_get_global_queue

    (DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

      

      UIImage *image = [UIImage imageWithContentsOfFile:photoFilePath];

      if(orientation == PhotoOrientationPortrait) {

        UIGraphicsBeginImageContext(CGSizeMake(180.0f, 120.0f));

        [image drawInRect:CGRectMake(0, 0, 120.0f, 180.0f)];

        thumbImage = UIGraphicsGetImageFromCurrentImageContext();

        UIGraphicsEndImageContext();

      } else {

        

        UIGraphicsBeginImageContext(CGSizeMake(120.0f, 180.0f));

        [image drawInRect:CGRectMake(0, 0, 180.0f, 120.0f)];

        thumbImage = UIGraphicsGetImageFromCurrentImageContext();

        UIGraphicsEndImageContext();

      }

      

      dispatch_async(dispatch_get_main_queue(), ^{

        

        [self.photosCache setObject:thumbImage forKey:photoName];

        cell.photoView.image = thumbImage;

      });

    });

  }

  

  return cell;

}

That completes the data source. But you’re not done yet. Go ahead and implement the delegate method. The delegate method will tell you which collection view was tapped, and you’ll display the photo in a separate form sheet.

Adding a Selected State to the Cell

First, add a selectedBackgroundView to your cell:

-(void) awakeFromNib {

  

  self.selectedBackgroundView = [[UIView alloc] initWithFrame:self.frame];

  self.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0.3

  alpha:0.5];

  

  [super awakeFromNib];

}

Now, implement the following three delegate methods and return YES for the delegate that asks if the collection view should select/highlight the said item.

UICollectionViewDelegate methods in MKViewController.m

- (BOOL)collectionView:(UICollectionView *)collectionView

   shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath {

  

  return YES;

}

- (BOOL)collectionView:(UICollectionView *)collectionView

   shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {

  

  return YES;

}

Now, build and run the app. Tap on a photo. You’ll see a selection as shown in Figure 7-3.

9781118449974-fg0703.eps

Figure 7-3 Screenshot of iOS Simulator showing cell selection

Handling Taps on Your Collection View Cell

That was easy, right? Now, show a detailed view when you tap on this item. Create a details controller in your storyboard. Add necessary outlets in your controller subclass (a UIImageView and a UILabel) and a property to pass the photo to be displayed from the collection view.

Now, create a segue by Ctrl-clicking the collection view controller and dragging it to your details controller.

Select the segue and set the identifier of your segue to something like MainSegue. Also change the style to “modal” and presentation to “Form Sheet.” You can leave the transition style to the default value or change it to whatever you prefer.

Implement the last delegate method, collectionView:didSelectItemAtIndexPath:.

collectionView:didSelectItemAtIndexPath: in MKViewController.m

-(void) collectionView:(UICollectionView *)collectionView

   didSelectItemAtIndexPath:(NSIndexPath *)indexPath {

  [self performSegueWithIdentifier:@”MainSegue” sender:indexPath];

}

Implement the prepareForSegue method.

prepareForSegue Method in MKViewController.m

-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

  

  NSIndexPath *selectedIndexPath = sender;

  NSString *photoName = [self.photosList

  objectAtIndex:selectedIndexPath.row];

  

  MKDetailsViewController *controller = segue.destinationViewController;

  controller.photoPath = [[self photosDirectory]

   stringByAppendingPathComponent:photoName];

}

Build and run the app and tap on a photo. You’ll see the Details view showing the photo you tapped.

That completes handling the delegate. Wait! That’s not the end of the road! You can also add multiple sections to the collection view and set decoration views and backgrounds to them. I’ll show you how to do this now.

Adding a “Header” and a “Footer”

Well, a collection view doesn’t call them headers and footers. When you open the UICollectionViewDataSource header file, you probably won’t find any methods that can be used to add these elements. A collection view calls these views supplementary views, and there are two kinds of supplementary views: UICollectionElementKindSectionHeader and UICollectionElementKindSectionFooter.

You add a supplementary view in your storyboard by selecting the CollectionView and enabling the “Section Header” or “Section Footer” and dragging a UICollectionReusableView from the object browser to your collection view. The next important step is to set an identifier to your supplementary view. You can do this by selecting the newly dragged UICollectionReusableView and setting the identifier on the Utilities panel.

Next, you add methods (in fact, just one method) to your UICollectionViewController subclass.

Collection View Datasource Method to Provide a Footer

- (UICollectionReusableView *)collectionView:(UICollectionView

   *)collectionView

           viewForSupplementaryElementOfKind:(NSString *)kind

                                 atIndexPath:(NSIndexPath *)indexPath {

  

  

  static NSString *SupplementaryViewIdentifier =

  @”SupplementaryViewIdentifier”;

  

  return [collectionView

   dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter

   withReuseIdentifier:SupplementaryViewIdentifier

   forIndexPath:indexPath];

}

This is slightly different from a table view where you create a section header in the data source method and return it. The collection view controller takes it to the next level by adding dequeue support to supplementary views as well.

Collection View Layout and Advanced Customization

The most important differentiator of a collection view and a table view is that a collection view doesn’t know how to lay out its items. It delegates the layout mechanics to a UICollectionViewLayout subclass. The default layout is a flow layout provided by the UICollectionViewFlowLayout class. This class allows you to tweak various settings through UICollectionViewDelegateFlowLayout protocol. For instance, you can change the scrollDirection property from vertical (the default) to horizontal and instantly get a horizontally scrolling collection view.

The most important part of UICollectionViewLayout is that by subclassing it, you can create your own custom layouts. For example, a “CoverFlowLayout”. UICollectionViewFlowLayout happens to be one such subclass that is provided by default from Apple. For this reason, collection view calls the “headers” and “footers” supplementary views. It’s a header if you’re using the built-in flow layout. It could be something else if you’re using a custom layout like, say, a cover flow layout.

Collection views are powerful because the layout is handled independent of the class, and this alone means that you’ll probably see a huge array of custom UI components, including springboard imitations, custom home screens, visually rich layouts like cover flow, pinterest-style UIs, and such. If you’re making an app that requires a rich layout, upgrade your minimum target to iOS 6 just for using collection views. Use it and try making simple applications using collection views, and you’ll gradually become a pro.

With that, it’s time to start the next important layout technology, Cocoa Auto Layout.

Cocoa Auto Layout

Cocoa Auto Layout was introduced last year with Mac OS X SDK. Apple has a long-standing history of testing new SDK features on Mac SDKs and bringing them to iOS the following year. For example, Apple introduced SceneKit.framework in the Mac OS X 10.8 SDK, and this might just find its way to iOS in the next release (iOS 7).

Auto Layout is a constraint-based layout engine that automatically adjusts the frames based on constraints set on your object. The layout model used prior to iOS 6 is called the “springs and struts” model. Although that worked efficiently for the most part, you still had to write code to lay out your subviews when trying to do custom layout during a rotation. Also, because Auto Layout is constraint-based and not frame-based, you can size your UI elements based on content automatically. This means that translating your UI to a new language just got easier. You don’t have to create multiple XIBs, one for every language anymore. Constraints are again instances of the NSLayoutConstraint class.

Auto Layout is supported only on iOS 6. This means that if you’re intending to support iOS 5 as a deployment target, well, you guessed it, you can’t use Auto Layout. Xcode 4.4.1, the latest at the time of this writing, creates every nib file with Auto Layout turned on by default, even if your minimum deployment target is not iOS 6. Using Auto Layout in unsupported operating systems will crash the whole app when you navigate to that view controller. Before submitting your apps, check every nib file and ensure that you haven’t—even accidentally—turned on Auto Layout for your iOS 5 apps.

Using Auto Layout

Whenever Apple introduces a new technology, you can use it in one of the following ways. You can either convert the whole application to use the new technology, or migrate it partially, or write new code using the new technology. With iOS 5, you followed one of these ways for ARC conversion and storyboards. In iOS 6, this applies to Auto Layout. You can migrate an existing application completely to Auto Layout, use it only for new UI elements, or do a partial conversion.

I recommend doing a complete rewrite, even if you’ve written lots of boilerplate layout code. With Auto Layout, you can almost completely get rid of it. Moreover, just by enabling Auto Layout in your nib file, Interface Builder automatically converts your auto-resize masks to equivalent Auto Layout constraints.

Getting Started with Auto Layout

Thinking in terms of Auto Layout might be difficult if you are a “pro” at using the struts and springs method. But for a newbie to iOS, Auto Layout might be easy. My first recommendation is to “unlearn” the struts and springs method.

Why do I say that? The human brain is an associative machine. When you actually lay out your UI elements, your mental model (or your designer’s mental model) is indeed “thinking” in terms of visual constraints. This happens not only when you are designing user interfaces; you do it subconsciously when you’re shifting furniture in your living room. You do it when you park your car in a parking lot. You do it when you hang a photo frame on your wall. In fact, when you were doing UI layout using the old springs and struts method, you were converting the relative, constraint-based layout in your mind to what springs and struts can offer, and you were writing code to do layout that cannot be done automatically. With Auto Layout, you can express how you think—by applying visual constraints.

Here’s what your designer may normally say:

“Place the button 10 pixels from the bottom.”

“Labels should have equal spacing between them.”

“The image should be centered horizontally at all times.”

“The distance between the button and the image should always be 100 pixels.”

“The button should always be at least 44 pixels tall.”

and so on . . .

Before Auto Layout, you were manually converting these design criteria to auto-resize mask constraints, and for criteria that cannot be easily done with auto-resize masks, you wrote layout code in the layoutSubviews method. With Auto Layout, most of these constraints can be expressed naturally.

For example, a constraint like “Labels should have equal spacing between them” is a constraint that cannot be implemented with the springs and struts method. You have to write layout code by calculating the frame sizes of the superview and calculating the frames of all the labels. The word “them” in the above-mentioned constraint makes the constraint relative to another label within the same view (a sibling view instead of the super view).

Relative Layout Constraints

Say hello to the Auto Layout’s relative layout constraint. In fact, one of the most powerful features of Auto Layout is the capability to descriptively define the constraints of a UI element with respect to the parent and with respect to its sibling. A button can have a constraint that positions itself based on the image above it. With springs and struts, you were able to control the anchor points and frames of an element based only on its superview.

Here’s an example. I’m going to make an app that displays nine images in a grid fashion as shown in Figure 7-4.

You can get the source code for this (AutoLayoutDemo) from the book’s website. Open the storyboard and turn off Auto Layout. Run the app and rotate your simulator (or device if you’re running on your device). Notice that in landscape mode, the images in the center row are not aligned the way you want them to be. This is because the springs and struts model allows you to anchor your elements relative only to the superview, whereas here the images in the center row are constrained to be equally spaced from the top row and bottom row images.

Now turn Auto Layout on. Run the app again and rotate the simulator. Notice that the images lay out properly on any orientation without you having to write a single line of code! When you turn on Auto Layout, Xcode by default converts the springs and struts-based resizing mask constraints to equivalent auto layout constraints, and in some cases (like this example), it works without you having to write layout code, including for layout animation.

9781118449974-fg0704.tif

Figure 7-4 Interface Builder showing a 3×3 grid of images

Well, that was simple. But that example didn’t really show you the power of Auto Layout. Here’s another one. Assume that you want to add a button just below each image view and set criteria that always places the buttons at the bottom of the image view regardless of its orientation, as illustrated in Figure 7-5.

If you were using the springs and struts model, this wouldn’t be an easy constraint. You have to calculate the position of your button every time the layout changes (in your layoutSubviews method) and animate it to the new position. Not for the weak-hearted. Moreover, layout code like this is not portable across apps, and adding a new UI element to this view requires a huge amount of changes.

Auto Layout makes this easy. Add a button. Select the button and the image that you want the button to position it to. You’ll now add vertical and horizontal spacing constraints to the button with respect to the image. This is shown in Figure 7-6.

9781118449974-fg0705.tif

Figure 7-5 Button placement below the image views

When you add these two constraints, as shown in Figure 7-6, and run the app, you’ll see that the button position remains the same with respect to the image in both orientations without you having to again lay out the UI by code.

The next important feature of Auto Layout is the ability to express your layout constraint by code using a syntactic language that Apple calls Visual Format Language.

9781118449974-fg0706.eps

Figure 7-6 Screenshot of Interface Builder showing how to add vertical and horizontal spacing constraints using Auto Layout

Visual Format Language

In this section, you learn about Visual Format Language and how to lay out UI by code using that language, the Auto Layout way, instead of calculating frames and repositioning them. So far you’ve been working with Interface Builder to lay out your subviews. Visual Format Language will be helpful if you’re using code to create and lay out the UI elements.

Adding a layout constraint to a view is easy. Create an NSLayoutConstraint instance and add it as a constraint to the view. You can create an NSLayoutConstraint instance using the Visual Format Language or using the class method.

Adding a Constraint to a View

[self.view addConstraint:

[NSLayoutConstraint constraintWithItem:self.myLabel

                              attribute:NSLayoutAttributeRight

                              relatedBy:NSLayoutRelationEqual

                                 toItem:self.myButton

                              attribute:NSLayoutAttributeLeft

                             multiplier:10.0

                               constant:100.0]];

Adding a Constraint Using Visual Format Language

NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(self.myLabel, self.myButton);

NSArray *constraints = [NSLayoutConstraint

                        constraintsWithVisualFormat:@”[myLabel]-100-

                        [myButton]”

                        options:0 metrics:nil

                        views:viewsDictionary];

[self.view addConstrints:constraints];

The second method is more expressive and lets you specify a constraint using an ASCII Art style. In the preceding example, you created a constraint that ensures that the space between the label and the button is always 100 pixels. The following is the visual format language syntax for specifying this constraint:

[myLabel]-100-[myButton]

Well, that was easy. Now, take a look at a more complicated example. The Visual Format Language is powerful and expressive, almost like regular expressions, yet readable. You can connect the labels and buttons to the superview using the following syntax:

|-[myLabel]-100-[myButton]-|

You can add a nested constraint to the button as follows:

|-[myLabel]-100-[myButton (>=30)]-|

This constraint will ensure that myButton will be at least 30 pixels at all orientations.

You can even add multiple constraints to the same button:

|-[myLabel]-100-[myButton (>=30, <=50)]-|

This constraint will ensure that myButton will be at least 30 pixels but not more than 50 pixels at all orientations.

Now, when you add conflicting constraints like the one here

|-[myLabel]-100-[myButton (>=30, ==50)]-|

Auto Layout will gracefully try to satisfy by ignoring the conflicting constraint. But when your UI layout cannot be performed without ambiguities, Auto Layout will crash. I show you two methods to handle debugging errors in the next couple of sections.

Debugging Layout Errors

When Auto Layout throws exceptions, you see something like the following on your console. You can simulate this crash from the example code for this chapter. Open the AutoLayoutDemo code and go to the Details view by tapping the Get iOS 6 Now button. Rotate your iPad to see this crash.

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)

(

    “<NSAutoresizingMaskLayoutConstraint:0x762ed60 h=--& v=--&

      H:[UIView:0x762b2b0(748)]>”,

    “<NSLayoutConstraint:0x762af50 V:[UIImageView:0x762b310(400)]>”,

    “<NSLayoutConstraint:0x762adc0 V:|-(304)-[UIImageView:0x762b310]   

     (Names: ‘|’:UIView:0x762b2b0 )>”,

    “<NSLayoutConstraint:0x762ad80 V:[UIImageView:0x762b310]-(300)-|   

     (Names: ‘|’:UIView:0x762b2b0 )>”

)

Will attempt to recover by breaking constraint

<NSLayoutConstraint:0x762af50 V:[UIImageView:0x762b310(400)]>

Break on objc_exception_throw to catch this in the debugger.

The methods in the UIConstraintBasedLayoutDebugging category on UIView

listed in <UIKit/UIView.h> may also be helpful.

The crash will list of the constraints Auto Layout is trying to satisfy. In the preceding case, there’s a constraint that the image height should be at least 400 pixels tall and 300 pixels from the top and bottom edge of the superview. When the iPad was in portrait mode, the layout engine was able to lay out the UI with all the constraints, but when you rotated it to landscape mode, it crashed because in landscape mode, the height of the iPad is much less and can’t satisfy the constraints provided. The log also says that it will try to recover by breaking a constraint.

You might not see crashes like this when you use Interface Builder because when you try to add a conflicting constraint, Interface Builder automatically adds several other constraints to counterbalance yours. In most cases, you will see such crashes when you use the Visual Format Language or manual code-based layout,.

The next common case for this crash is when your view automatically translated auto-resizing masks to constraints. When that happens, you’ll see a lot more constraints (including many that weren’t directly created by you) in the crash log. Debugging will be easier if you turn off the option that translates resizing masks to constraints on a per-view basis. You do so by calling the method

[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];

While troubleshooting crashes for which there are log entries you don’t understand, set translatesAutoResizingMaskIntoConstraints to NO, one-by-one, for every view that you think might be offending, and when you spot the offending view, remove the translated constraints and write your own.

Summary

In this chapter, you learned about two powerful techniques that will revolutionize the way you write custom controls and layouts. Auto Layout, by itself, may not be a compelling reason to develop for only iOS 6, but if your app is data-centric (an RSS reader or a Twitter client, for example), collection views should be a compelling reason to support only iOS 6 in your apps. Auto Layout might be interesting down the line, say after five or six months when most users have already moved to iOS 6. To end users, Auto Layout may not offer benefits, but for developers, using Auto Layout means deleting tons of layout code from your controllers. When iOS 6 is out of NDA, you’ll be seeing more and more third-party, open source controls (or controllers) and layout classes using collection views that are easier to use and understand. Collection views are a new building block element like table views and you should start learning them to take your apps to the next level.

Further Reading

Apple Documentation

The following documents are available in the iOS Developer Library at developer.apple.com or through the Xcode Documentation and API Reference.

UICollectionViewController Class Reference

UICollectionViewLayout Class Reference

Cocoa Auto Layout Guide

WWDC Sessions

WWDC 2012, “Session 205: Introducing Collection Views”

WWDC 2012, “Session 219: Advanced Collection Views and Building Custom Layouts”

WWDC 2012, “Session 202: Introduction to Auto Layout for iOS and OS X”

WWDC 2012, “Session 228: Best Practices for Mastering Auto Layout”

WWDC 2012, “Session 232: Auto Layout by Example”

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.155.216