Have you ever wondered how some of Apple's apps like iBooks have these unique "shelf" stylings, groups and rows of products or photos? Ever wanted to duplicate that functionality? Now, the process is much easier.

Something new and exciting available in iOS 6 is UICollectionView. UICollectionView is similar to UITableView, except think of them as table views on steroids. If you need something a bit more exciting than just plain rows, UICollectionView can help. In fact, you can even create advanced layouts like circles, different cell sizes for each item (think horizontal or vertical photo-books), or even adjust their transforms on a cell-by-cell basis. But first, lets learn a bit more about UICollectionView.

Apple made UICollectionView very easy to use. The concept is simple and each UICollectionView has three main components:

  1. Cells: cells are the content you wish to display, and inherits their format (bookshelf, circle, line, etc) from a layout that you specify
  2. Supplementary Views: supplementary views are views like labels and section headers/footers
  3. Decoration Views: decoration views are just that – decorations like a bookshelf graphic or a background

Setting up a collection view is very similar to a table view. The first step is to tell your view controller that you conform to the UICollectionViewDelegateas demonstrated below:

<span class="kw1">@interface</span> ViewController <span class="sy0">:</span> UICollectionViewController 

Since UICollectionView uses the QuartzCore framework, you need to add that into the project as well.

With the view defined, jump over to the implementation file and implement the numberOfSectionsInCollectionView: andcollectionView:numberOfItemsInSection: delegate methods. This tells the collection view how many sections are included in the view and how many items are in each section, respectively.

<span class="sy0">-</span><span class="br0">(</span>NSInteger<span class="br0">)</span>numberOfSectionsInCollectionView<span class="sy0">:</span><span class="br0">(</span>UICollectionView <span class="sy0">*</span><span class="br0">)</span>collectionView <span class="br0">{</span>
 <span class="kw1">return</span> <span class="nu0">4</span>;
<span class="br0">}</span>
 
<span class="sy0">-</span> <span class="br0">(</span>NSInteger<span class="br0">)</span>collectionView<span class="sy0">:</span><span class="br0">(</span>UICollectionView <span class="sy0">*</span><span class="br0">)</span>view numberOfItemsInSection<span class="sy0">:</span><span class="br0">(</span>NSInteger<span class="br0">)</span>section <span class="br0">{</span>
 <span class="kw1">return</span> <span class="nu0">4</span>;
<span class="br0">}</span>
 
 <span class="sy0">-</span> <span class="br0">(</span>UICollectionViewCell <span class="sy0">*</span><span class="br0">)</span>collectionView<span class="sy0">:</span><span class="br0">(</span>UICollectionView <span class="sy0">*</span><span class="br0">)</span>cv cellForItemAtIndexPath<span class="sy0">:</span><span class="br0">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSIndexPath_Class/"><span class="kw5">NSIndexPath</span></a> <span class="sy0">*</span><span class="br0">)</span>indexPath; <span class="br0">{</span>
 Cell <span class="sy0">*</span>cell <span class="sy0">=</span> <span class="br0">[</span>cv dequeueReusableCellWithReuseIdentifier<span class="sy0">:</span><span class="co3">@</span><span class="st0">"MY_CELL"</span> forIndexPath<span class="sy0">:</span>indexPath<span class="br0">]</span>;
 cell.label.text <span class="sy0">=</span> <span class="br0">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span class="kw5">NSString</span></a> stringWithFormat<span class="sy0">:</span><span class="co3">@</span><span class="st0">"%d"</span>,indexPath.item<span class="br0">]</span>;
 <span class="kw1">return</span> cell;
<span class="br0">}</span>

Something else that is new with UICollectionView is the modification to how cells are initialized and reused. We no longer have to instantiate the cell because Apple takes care of that for us. You may have noticed a few differences with the code above. Namely, the lack of a (!cell) in thecellForItemAtIndexPath:, and this code in the viewDidLoad:

<span class="br0">[</span>self.collectionView registerClass<span class="sy0">:</span><span class="br0">[</span>Cell class<span class="br0">]</span> forCellWithReuseIdentifier<span class="sy0">:</span><span class="co3">@</span><span class="st0">"MY_CELL"</span><span class="br0">]</span>;

Now, we have a basic collection view setup, but as you can see it doesn't look at all like a finished product. In fact, if you built the app right now, the screen would just be blank.

First, we want to tell the view what each of its cells looks like. So lets create and add a Cell.h/.m file to the project. Then add a single property named label to our Cell, which will be used to display a numerical value on the cell:

<span class="kw1">@interface</span> Cell <span class="sy0">:</span> UICollectionViewCell
 
<span class="kw1">@property</span> <span class="br0">(</span>retain, nonatomic<span class="br0">)</span> UILabel<span class="sy0">*</span> label;
 
<span class="kw1">@end</span>

and then be sure to initialize the label in the cells implementation like so.

<span class="kw1">@implementation</span> Cell
 
<span class="sy0">-</span> <span class="br0">(</span><span class="kw4">id</span><span class="br0">)</span>initWithFrame<span class="sy0">:</span><span class="br0">(</span>CGRect<span class="br0">)</span>frame
<span class="br0">{</span>
 self <span class="sy0">=</span> <span class="br0">[</span>super initWithFrame<span class="sy0">:</span>frame<span class="br0">]</span>;
 <span class="kw1">if</span> <span class="br0">(</span>self<span class="br0">)</span> <span class="br0">{</span>
 self.label <span class="sy0">=</span> <span class="br0">[</span><span class="br0">[</span>UILabel alloc<span class="br0">]</span> initWithFrame<span class="sy0">:</span>CGRectMake<span class="br0">(</span><span class="nu0">0.0</span>, <span class="nu0">0.0</span>, frame.size.width, frame.size.height<span class="br0">)</span><span class="br0">]</span>;
 self.label.textAlignment <span class="sy0">=</span> NSTextAlignmentCenter;
 self.label.textColor <span class="sy0">=</span> <span class="br0">[</span>UIColor blackColor<span class="br0">]</span>;
 self.label.font <span class="sy0">=</span> <span class="br0">[</span>UIFont boldSystemFontOfSize<span class="sy0">:</span><span class="nu0">35.0</span><span class="br0">]</span>;
 self.label.backgroundColor <span class="sy0">=</span> <span class="br0">[</span>UIColor whiteColor<span class="br0">]</span>;
 
 <span class="br0">[</span>self.contentView addSubview<span class="sy0">:</span>self.label<span class="br0">]</span>;;
 <span class="br0">}</span>
 <span class="kw1">return</span> self;
<span class="br0">}</span>
 
<span class="kw1">@end</span>

So, now we have a custom cell that displays a centered label. The last piece is to tell the UICollectionView what layout you want to display the cells in. For that, create a new class named CollectionViewLayout.h/.m that subclasses UICollectionViewFlowLayout. A UICollectionViewFlowLayout is a simple layout that allows the views to "Flow", whether horizontally or vertically. It organizes items in a grid, flowing the items from each row or column to the next, as many as will fit in each. Cells do not need to be the same size to utilize UICollectionViewFlowLayout.

There are also a few other methods to register the cell:

  • -(void)registerClass:forCellWithReuseIdentifier:
  • -(void)registerClass:forSupplementaryViewOfKind:withReuseIdentifier:
  • -(void)registerNib:forCellWithReuseIdentifier:
  • -(void)registerNib:forSupplementaryViewOfKind:withReuseIdentifier:

the registerNib function is very interesting, because now you can make your own custom NIB file that is reused as a cell, all with a few lines of code – and with a completely new layout.

The interface definition is straight-forward as you can see below.

<span class="kw1">@interface</span> CollectionViewLayout <span class="sy0">:</span> UICollectionViewFlowLayout
 
<span class="kw1">@end</span>

The meat of creating a nice looking collection view is in the layout itself. As you can see in the following example, there are a number of configurable options available to you. You can set the padding, margins, item sizes, scrolling direction and more.

<span class="co1">#import "CollectionViewLayout.h"</span>
 
<span class="kw1">@implementation</span> CollectionViewLayout
 
<span class="sy0">-</span><span class="br0">(</span><span class="kw4">id</span><span class="br0">)</span>init <span class="br0">{</span>
 self <span class="sy0">=</span> <span class="br0">[</span>super init<span class="br0">]</span>;
 <span class="kw1">if</span> <span class="br0">(</span>self<span class="br0">)</span> <span class="br0">{</span>
 self.itemSize <span class="sy0">=</span> CGSizeMake<span class="br0">(</span><span class="nu0">250</span>, <span class="nu0">250</span><span class="br0">)</span>;
 self.scrollDirection <span class="sy0">=</span> UICollectionViewScrollDirectionVertical;
 self.sectionInset <span class="sy0">=</span> UIEdgeInsetsMake<span class="br0">(</span><span class="nu0">100</span>, <span class="nu0">0.0</span>, <span class="nu0">100</span>, <span class="nu0">0.0</span><span class="br0">)</span>;
 self.minimumLineSpacing <span class="sy0">=</span> <span class="nu0">50.0</span>;
 <span class="br0">}</span>
 <span class="kw1">return</span> self;
<span class="br0">}</span>
 
<span class="sy0">-</span> <span class="br0">(</span><span class="kw4">BOOL</span><span class="br0">)</span>shouldInvalidateLayoutForBoundsChange<span class="sy0">:</span><span class="br0">(</span>CGRect<span class="br0">)</span>oldBounds <span class="br0">{</span>
 <span class="kw1">return</span> <span class="kw2">YES</span>;
<span class="br0">}</span>
 
<span class="sy0">-</span><span class="br0">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span class="kw5">NSArray</span></a><span class="sy0">*</span><span class="br0">)</span>layoutAttributesForElementsInRect<span class="sy0">:</span><span class="br0">(</span>CGRect<span class="br0">)</span>rect <span class="br0">{</span>
 <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span class="kw5">NSArray</span></a><span class="sy0">*</span> array <span class="sy0">=</span> <span class="br0">[</span>super layoutAttributesForElementsInRect<span class="sy0">:</span>rect<span class="br0">]</span>;
 CGRect visibleRect;
 visibleRect.origin <span class="sy0">=</span> self.collectionView.contentOffset;
 visibleRect.size <span class="sy0">=</span> self.collectionView.bounds.size;
 
 <span class="kw1">return</span> array;
<span class="br0">}</span>
 
<span class="sy0">-</span> <span class="br0">(</span>CGPoint<span class="br0">)</span>targetContentOffsetForProposedContentOffset<span class="sy0">:</span><span class="br0">(</span>CGPoint<span class="br0">)</span>proposedContentOffset withScrollingVelocity<span class="sy0">:</span><span class="br0">(</span>CGPoint<span class="br0">)</span>velocity <span class="br0">{</span>
 CGFloat offsetAdjustment <span class="sy0">=</span> MAXFLOAT;
 CGFloat horizontalCenter <span class="sy0">=</span> proposedContentOffset.x <span class="sy0">+</span> <span class="br0">(</span>CGRectGetWidth<span class="br0">(</span>self.collectionView.bounds<span class="br0">)</span> <span class="sy0">/</span> <span class="nu0">2.0</span><span class="br0">)</span>;
 
 CGRect targetRect <span class="sy0">=</span> CGRectMake<span class="br0">(</span>proposedContentOffset.x, <span class="nu0">0.0</span>, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height<span class="br0">)</span>;
 <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span class="kw5">NSArray</span></a><span class="sy0">*</span> array <span class="sy0">=</span> <span class="br0">[</span>super layoutAttributesForElementsInRect<span class="sy0">:</span>targetRect<span class="br0">]</span>;
 
 <span class="kw1">for</span> <span class="br0">(</span>UICollectionViewLayoutAttributes<span class="sy0">*</span> layoutAttributes <span class="kw1">in</span> array<span class="br0">)</span> <span class="br0">{</span>
 CGFloat itemHorizontalCenter <span class="sy0">=</span> layoutAttributes.center.x;
 <span class="kw1">if</span> <span class="br0">(</span>ABS<span class="br0">(</span>itemHorizontalCenter <span class="sy0">-</span> horizontalCenter<span class="br0">)</span> <span class="sy0">&</span>lt; ABS<span class="br0">(</span>offsetAdjustment<span class="br0">)</span><span class="br0">)</span> <span class="br0">{</span>
 offsetAdjustment <span class="sy0">=</span> itemHorizontalCenter <span class="sy0">-</span> horizontalCenter;
 <span class="br0">}</span>
 <span class="br0">}</span>
 <span class="kw1">return</span> CGPointMake<span class="br0">(</span>proposedContentOffset.x <span class="sy0">+</span> offsetAdjustment, proposedContentOffset.y<span class="br0">)</span>;
<span class="br0">}</span>
 
<span class="kw1">@end</span>

Now lets import Cell.h and CollectionViewLayout.h in our ViewController class implementation, and put this into our viewDidLoad: in the same file:

<span class="br0">[</span>self.collectionView registerClass<span class="sy0">:</span><span class="br0">[</span>Cell class<span class="br0">]</span> forCellWithReuseIdentifier<span class="sy0">:</span><span class="co3">@</span><span class="st0">"MY_CELL"</span><span class="br0">]</span>;

That was certainly a lot of code, let's step through it. First, in the init method we set the itemSize, scrollDirection, sectionInset and minimumLineSpacing. Secondly, implementing shouldInvalidateLayoutForBoundsChange: method instructs the layout to reload itself if the bounds change (such as an item getting bigger or smaller. In layoutAttributesForElementsInRect:, we setup an array of the visual objects, which contains their attributes. And finally, intargetContentOffsetForProposedContentOffset: we tell the view at what point to stop scrolling.

With the layout complete, make the following additions to the AppDelegate implementation to create a collection view controller that uses the layout created in the previous snippet and add it as the root view.

<span class="co1">#import "AppDelegate.h"</span>
<span class="co1">#import "CollectionViewLayout.h"</span>
<span class="co1">#import "ViewController.h"</span>
 
<span class="kw1">@implementation</span> AppDelegate
 
<span class="sy0">-</span> <span class="br0">(</span><span class="kw4">BOOL</span><span class="br0">)</span>application<span class="sy0">:</span><span class="br0">(</span>UIApplication <span class="sy0">*</span><span class="br0">)</span>application didFinishLaunchingWithOptions<span class="sy0">:</span><span class="br0">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span class="kw5">NSDictionary</span></a> <span class="sy0">*</span><span class="br0">)</span>launchOptions <span class="br0">{</span>
 self.window <span class="sy0">=</span> <span class="br0">[</span><span class="br0">[</span>UIWindow alloc<span class="br0">]</span> initWithFrame<span class="sy0">:</span><span class="br0">[</span><span class="br0">[</span>UIScreen mainScreen<span class="br0">]</span> bounds<span class="br0">]</span><span class="br0">]</span>;
 
 CollectionViewLayout <span class="sy0">*</span>collectionViewLayout <span class="sy0">=</span> <span class="br0">[</span><span class="br0">[</span>CollectionViewLayout alloc<span class="br0">]</span> init<span class="br0">]</span>;
 self.viewController <span class="sy0">=</span> <span class="br0">[</span><span class="br0">[</span>ViewController alloc<span class="br0">]</span> initWithCollectionViewLayout<span class="sy0">:</span>collectionViewLayout<span class="br0">]</span>;
 
 self.window.rootViewController <span class="sy0">=</span> self.viewController;
 <span class="br0">[</span>self.window makeKeyAndVisible<span class="br0">]</span>;
 <span class="kw1">return</span> <span class="kw2">YES</span>;
<span class="br0">}</span>
 
<span class="kw1">@end</span>

Now build the project and see what happens. If all works as expected, you will have 4 sections with 4 cells each, that are scrollable vertically similar to the screenshot below.

Now, to really drive-home the flexibility the UICollectionView class provides, make the following change to to the init method implementation of theCollectionViewLayout class.

self.scrollDirection <span class="sy0">=</span> UICollectionViewScrollDirectionVertical;

to:

self.scrollDirection <span class="sy0">=</span> UICollectionViewScrollDirectionHorizontal;

Re-run the project and you should have horizontal scrolling. It really is that simple.

There is also a new function that you need to call if something in your layout changes. By calling [self invalidateLayout] you are telling the collection view that it needs to re-query the layout information. You would do this if you wanted to implement custom events such as resizing a cell with a touch, a gesture, or when another action is taken. This is similar to the reloadData method on UITableView.

With this new class, Apple has really given developers access to a very powerful new tool. You can use UICollectionView to integrate new and exciting layouts into our apps, resulting in a much better experience for your users.

I have also attached the sample project that we walked through in this post, feel free to download and poke around.

If anyone has questions, I can be reached at: pdakessian@captechconsulting.com.