Since finalising the iOS 6 changes for Learning Tree’s Building iPhone® and iPad® Applications: Extended Features course, I’ve been taking the opportunity to do something different (!) and experiment with the new collection view. It’s fast becoming one of my favourite iOS controls!
When I first read the description and tried it on the iOS 6 beta, my interpretation of it was as a grid control but there’s much more to it than that. In this post I want to explore it’s versatility and show you how it can be used to create efficient horizontal paging interfaces.
Paging Prior to Collection Views
Prior to iOS 6 the way to create a horizontal paged view in the style of the standard Weather app was to use a UIScrollView
. For a fixed number of pages, you could lay out a wide view using a XIB in Interface Builder. For more dynamic interfaces or interfaces that needed more than a few pages, it involved creating a controller that would generate and reuse views in much the same way that a table view controller reuses cells.
Paging with Collection Views
A UICollectionView
might appear at first sight to be a grid control but, because it supports horizontal scrolling and paging, it can also deal with the Weather app style interface. The bonus is that there is very little code to write because a the collection view’s data source implementation deals with creating and reusing the pages.
Let’s create a simple paged app and, to emphasise how little code there is, we’ll work without XIBs and storyboards.
Start a new iPhone project in Xcode, choose the Empty Application template as a starting point and disable the two landscape orientations on the summary page. Create a new subclass of UIViewController
but don’t edit it just yet — we’ll come back to this later.
Application Delegate
Set up an application delegate with the collection view controller as the root. To save space, I’ll let you figure out the #imports
you need.
AppDelegate.m
@implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { CGRect frame = [[UIScreen mainScreen] bounds]; self.window = [[UIWindow alloc] initWithFrame:frame]; self.window.rootViewController = [ [CollectionViewController alloc] init ]; [self.window makeKeyAndVisible]; return YES; } @end
Custom Collection View Cell
Now create a custom collection view cell with a label:
CustomCell.h
@interface CustomCell : UICollectionViewCell @property (strong, nonatomic) UILabel *label; @end
Set up the label with white text and a clear background and add it to the cell. This is just an arbitrary size and we aren’t worrying about autosizing and rotation for this quick example.
CustomCell.m
@implementation CustomCell - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 300, 24) ]; self.label.textColor = [UIColor whiteColor]; self.label.backgroundColor = [UIColor clearColor]; [self addSubview:self.label]; } return self; } @end
Collection View Controller
Now we can implement the collection view controller created earlier. Because we are using 100% code here, we’ll need to implement the flow layout’s delegate protocol so that we can size the cells. Add that protocol declaration to the interface file (you’ll probably get more on each line), along with the delegate and data source protocols for the collection view:
CollectionViewController.h
@interface CollectionViewController : UIViewController <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout> @property (strong, nonatomic) UICollectionView *collectionView; @end
To create the base view, we need to use the loadView
method. The flow layout is the standard layout manager for a collection view. The key to making this a paged interface is to set the pagingEnabled
property on the collection view.
- (void)loadView { self.view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds] ]; // Create a flow layout for the collection view that scrolls // horizontally and has no space between items UICollectionViewFlowLayout *flowLayout = [ [UICollectionViewFlowLayout alloc] init ]; flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; flowLayout.minimumLineSpacing = 0; flowLayout.minimumInteritemSpacing = 0; // Set up the collection view with no scrollbars, paging enabled // and the delegate and data source set to this view controller self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:flowLayout ]; self.collectionView.showsHorizontalScrollIndicator = NO; self.collectionView.pagingEnabled = YES; self.collectionView.delegate = self; self.collectionView.dataSource = self; [self.view addSubview:self.collectionView]; }
The rest of the setup of the collection view is done in the viewDidLoad
method. This registers a prototype cell class for the collection view — the same idea as creating a prototype cell in a storyboard.
CollectionViewController.m
- (void)viewDidLoad { [super viewDidLoad]; // Register a prototype cell class for the collection view [self.collectionView registerClass:[CustomCell class] forCellWithReuseIdentifier:@"Cell" ]; }
To feed the collection view with our pages, we implement the methods of the collection view’s data source protocol. Just as we would if we were creating a table view. PAGES
is just a #define
constant declared at the top of the implementation file to indicate how many pages we would like (6 is a good start).
CollectionViewController.m
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return PAGES; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { // Dequeue a prototype cell and set the label to indicate the page CustomCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath ]; cell.label.text = [NSString stringWithFormat:@"Page %d", indexPath.row + 1 ]; // To provide a good view of pages, set each one to a different color CGFloat hue = (CGFloat)indexPath.row / PAGES; cell.backgroundColor = [UIColor colorWithHue:hue saturation:1.0f brightness:0.5f alpha:1.0f ]; return cell; }
Then all that remains is to implement a method of the flow layout delegate to tell the collection view’s layout manager how big we want the cells. To make pages, we just make the cells fill the collection view’s bounds.
CollectionViewController.m
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { return collectionView.bounds.size; } @end
That’s all there is to it! Run it and see. That’s a 100% code implementation of a horizontal paged application explained and most of it reproduced in 900 words!
Next week, we’ll see how to add a page control (the dots) to complete the example.