Paging With Collection Views (Part 1)

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.

Richard Senior

PS – Have a look at our brand new course – Introduction to Swift Programming for Mac/iPhone/iPad, available in-class or online.

Type to search blog.learningtree.com

Do you mean "" ?

Sorry, no results were found for your query.

Please check your spelling and try your search again.