Hostinicon
GET HOSTING FROM $3.95/MO PLUS A FREE YEAR ON TUTS+ (RRP $180). HURRY OFFER LIMITED. Check it out
Advertisement

A Springboard-Like Layout With the UICollectionView Class

by
Gift

Get a free year on Tuts+ this month when you purchase a Siteground hosting plan from $3.95/mo

This tutorial will teach you how to use the iOS 6 UICollectionView class to build a "Springboard" layout that looks and acts like the iOS Home Screen! Along the way, you'll gain a solid grasp of collection view fundamentals in order to create your own grid based layouts.


Project Preview


Collection View Overview

Collection Views are an exciting new addition to the UIKit API in iOS 6. Some platform pundits are of the opinion that collection views are poised to become as fundamental and pervasive a component in iOS app design as table views have been (and your author is humbly inclined to agree!). Collection views and table views share a lot of common design philosophy (not to mention a similar API) because they serve a common purpose - to present a collection of related data. It is best not to think of collection views as a replacement to table views, but rather as a new offering of a more generic and flexible architecture for interacting with non-tabular data sets.

Because collection views are intended to be flexible, extensible and configurable, the architecture and hence API may seem to be a bit daunting at first. There are a number of interacting classes that one needs to know about. Depending on how simple or complex your requirements are, you might need to subclass some of these classes to achieve the results you want. Fortunately, the API is designed from the ground up to help with that. We’ll see several concrete examples of this later in the tutorial.

Let’s start by taking a brief overview of the main classes in the collection view framework. In the collection view world, a concrete unit of data is visually encapsulated by a UICollectionViewCell instance. The UICollectionView class provides a scrollable container that holds a collection of cells. While the collection view is aware of the position of cells, their visibility (or lack thereof) is not actually the responsibility of the class. The responsibility of handling the layout falls on the shoulders of the UICollectionViewLayout class. By switching the layout class for the same collection view, we could cause the same data to be displayed in a dramatically different way.

Speaking of visual elements, besides cells there are two others: supplementary views and decoration views. Data being displayed might be logically organized into sections, and supplementary views might be used to supply section-wise "metadata" (think headers and footers in a table view). On the other hand, decoration views are "ornamental" rather than data-driven: think of the bookshelf backdrop in the iBooks app. Of course, I’ve just mentioned these for the sake of completeness. The UI for this project's tutorial will be comprised entirely of cell elements and there won’t be any sections.

The UICollectionViewLayout is not restricted to a particular type of layout, but it ships with a subclass, UICollectionViewFlowLayout, that has been designed for constructing both line and grid-based layouts.

Much like table views, a collection view interacts with the underlying data through a data source and responds to UI events with the help of a delegate.

The layout class can manage the layout in a couple of ways - by setting global properties on cells or by means of delegation. The latter endows the layout class with finer grained control of the appearance of the UI.

There are of course some more classes to be mentioned, and I’ll come to them later.


Tutorial Approach

Let’s talk about appearances first: in this tutorial, we’re going to create a UI that looks like the iPhone/iPad home screen (also called the SpringBoard). It will be comprised of a scrollable grid of icons. Swiping left or right will present a new page of icons. Just like the SpringBoard, the interface will have a static background (a "wallpaper") behind it. Not only that, we’ll also implement icon deletion: long-pressing on any of the icons will trigger deletion mode. The icons will then start quaking (from fear of being deleted!), and a close button will present itself on each of them. Clicking on which will cause the icon to disappear from the grid. Clicking on an icon itself will modally present a new view that can be dismissed with a button to bring back our spring board view.

Behind the scenes, the UI is supported by a very simple data model (which we shall introduce shortly). This is so that we can concentrate on the interface implementation, with minimum added complexity thrown in by the "model" end of the MVC architecture. The icons will present part of the data (like the cells do in table views) and the view presented modally upon clicking on any icon will present the rest of our data.


A Barebones Implementation

Since this is (presumably) the first time we’re designing a layout with collection views, it’s easy to get swamped with lots of interacting objects and lose the bigger picture. To avoid this, I want to kick things off with a minimalistic implementation that still fulfils our essential requirement of a grid-based scrolling layout, and then explore how we can harness the power of the collection view framework to polish our UI and introduce new features.

Fire up Xcode, create a new project with an "Empty Template" (we’ll do everything in code for this tutorial) and call it "SpringBoardLayoutTut". Make sure it’s an iPad project, and that ARC is enabled.

New project

Deselect all modes except Portrait from "Supported Interface Orientations".

Portrait mode

Replace all the code in AppDelegate.m with the following:

Build the app. You should get a (very) basic grid interface.

Very basic grid UI

The above code is only meant to illustrate that a basic grid layout is available pretty much out-of-the-box with the UICollectionViewFlowLayout class. It is not meant to illustrate how you would actually structure your code; that’s what the rest of the tutorial is for!

If you know your table views, a lot of the above code should look familiar.

Our root view controller is an instance of UICollectionViewController, which comes with a UICollectionView instance as its view, and is, by default, set to be the collection view’s data source and delegate. We register the UICollectionViewCell class with the collection view controller and give it an identifier. The UICollectionViewCell is meant to be subclassed, as we shall soon, which is why the registration/identification step is needed. We also create an instance of the UICollectionViewFlowLayout, set it as our collection view’s layout, and set some properties on it defining how our cells will be sized and positioned on the grid. Our "data" just consists of a hundred integers. Note the familiar cell dequeuing step from table views, which recycles cells that have been moved off-screen to save on memory and cell creation. Unlike in iOS 5, the -dequeueReusableCellWithReuseIdentifier: method automatically returns a new cell if none exist in the reuse queue. When a cell is due to appear on the screen, a label is attached to it that displays one of the numbers from our integer list. Clicking on a cell just gives an acknowledgement in the form of a log message.

To reiterate, the point of the above code was just to show you that the basic grid layout is very easy to achieve with collection views and the flow layout class.

The rest of the tutorial is focused on making the UI look pretty, as well as more interactive and functional.


Creating a Simple Data Model

Let's get down to business. We'll start by creating a very simple data model, which consists of an array containing the names of all the font families installed on iOS, and a dictionary containing the fonts themselves, keyed by their respective font families.

Create an NSObject subclass called SimpleModel and replace the contents of SimpleModel.h and SimpleModel.m with the following:


Customizing the Icon

Our second order of business is to subclass UICollectionViewCell in order to make our icons look more like, well, icons!

Add the QuartzCore framework to your project. We’ll need this because we’ll be twiddling with CALayer properties and animating them.

Adding the QuartzCore framework

Create a new Objective-C file, make it a UICollectionViewCell subclass and call it Icon. Replace the code in the Icon.h and Icon.m files with the following:

Our icon cell's content view is inset with a slightly smaller view (added as a subview) which serves to hold our UILabel instance which is the main visible feature of our icon. The reason for using the inset subview is so we can later add a delete button that visually extends beyond the bounds of the label (so that it looks cool) yet lies within the bounds of the invisible content view (because making a control register touches when its bounds rectangle lies outside its parent view is a bit tricky and we would like to avoid that).

I’ve commented out some things that aren’t part of our app as it stands currently. I’ll ask you to uncomment them later. Alternatively, we’ll be filling in code wherever it says // INSERT ... SNIPPET HERE. I’ve done things this way so we can work on our app progressively instead of getting bogged down with all the details at once.


Customizing the Layout

Create a new Objective-C class file, make it a UICollectionViewFlowLayout subclass and name it SpringboardLayout. Replace the code in SpringboardLayout.h and SpringboardLayout.m with the following:


Creating a View Controller

Create a new Objective-C class file. Call the class ViewController and make it a subclass of UICollectionViewController. Replace the code in ViewController.m with the following:


Configuring the App Delegate

We’re almost ready to build the new and improved version of our interface.

Add the following two images to your project: Image One, Image Two. They’re going to serve as the backdrop to our springboard-like interface (thanks to Fabio of www.999wallpapers.com for generously letting me use them!).

Replace the contents of AppDelegate.m with the following code that creates and configures the view controller and assigns it to a layout:

Build the app. The UI should have a more sophisticated look and behavior.

A better looking UI

A Brief Recap - and Looking Ahead

Let’s take a step back and review what we’ve done so far, and how the code we’ve written translates our requirements in terms of the framework.

We created an Icon class that knows how to draw itself, and has a label that we can populate with our data. The details of our layout are managed by the SpringboardLayout class, which we subclassed from UICollectionViewLayout, which is intended for building line or grid based layouts. At this moment, if you were to make the observation that we didn’t really need to subclass the UICollectionViewFlowLayout class as we haven’t really extended it’s behaviour in any way, only set a few of its properties, well, you’d be smack on! Since all our cells are visually identical, we were able to get away by just setting some global properties on the layout class to get the grid-like arrangement we wanted, and - if that’s all we wanted - we could have done that on a UICollectionViewFlowLayout instance itself (as we did the first time 'round). We’ve subclassed UICollectionViewFlowLayout in anticipation of the functionality that we’ll be adding to our UI shortly.

At this point, I’ll mention that the other way of passing information to the layout is through delegation. This would be useful if we wanted our cells to have different sizes, for instance, or if we had multiple sections in our UI and we wanted a slightly different look for each section.

The actual data presentation uses the familiar data source and delegation mechanisms that we know and love from table views. The UICollectionViewController instance acts as both by default. The collection view determines the number of cells it contains and the contents of each cell by querying the data source, and it informs the delegate when the user taps on a cell so that it can respond appropriately. In our example, we respond by instantiating and modally presenting a view controller that displays a list of the font faces corresponding to the font family in the clicked cell.

Now, let’s consider our next objective, and continue to organize our code so that each class in the framework carries out the responsibilities it’s been designed for, no more and no less.


Implementing Deletion Mode

In Icon.h, uncomment the line:

In Icon.m, uncomment:

Then insert the following block of code at the appropriate place:

We could have included our close button as a bitmap image with our app but since it's fairly simple to create as a vector graphic with UIBezierPath (which we then convert to a bitmap), we’ve done that instead. One of the advantages of this approach (i.e. creating a vector path and then rasterizing) is that we don’t need to worry about providing multiple images for devices at different resolutions.

Let’s create the target-action method for our close button.

In ViewController.m, insert the following code snippets:

and

Basically, our animation causes our icon to rock (uneasily) from side-to-side. By adding a smaller random time offset to each icon, we prevent the icons from animating in sync, which would have weakened the illusion we’re going for.

If you want to take a peek at what the animation looks like, stick in the statement [self startQuivering]; at the end of the if(self) {…} block in the Icon init method. Just be sure to remove it afterward, though! The screenshot below shows the delete button and the icons in different phases of the rotation animation.

A snapshot of how the cells would look in deletion mode

We now want to introduce a deletion mode into our app that is activated and deactivated by the appropriate gestures, instead of having the delete button permanently active.

It seems reasonable to make the collection view controller responsible for activating and deactivating the deletion mode. Let’s introduce a boolean variable to keep track of the mode, and override a delegate method that indicates whether it is permitted for a cell the user taps on to be selected or not. We’ll make this method return YES or NO depending on the value of the boolean variable.

In ViewController.m, uncomment the declaration:

Next add the following method at the indicated place in the file:

A bit further down, we’ll implement a gesture recognizer that can change the value of this variable.

Now then, how do we pass this information to the SpringboardLayout class? We might consider defining a corresponding boolean property on the layout class that the view controller can change. But this creates an unnecessary coupling between the view controller and layout class. Plus, we’ll need to ensure that we synchronise changes in both the property values (i.e. in the view controller and layout). Design-wise, a much better solution is to use delegation: the method required by the protocol lets the layout query its delegate about the state of the app. In fact, by defining a protocol here, we’re mimicking or, rather, extending Apple’s own approach wherein they’ve defined UIViewControllerDelegateFlowLayout that can supply information to the UIViewControllerFlowLayout class. All of the methods in this protocol are optional. If not implemented, the layout falls back on the global properties (that we in fact have set), as I mentioned earlier. A technical point here is that this protocol already extends the UICollectionViewDelegate protocol, and we’re going to be extending it further.

Our protocol will contain a method that the layout can query to determine whether the deletion mode is active or not.

In SpringboardLayout.h add the following protocol declaration:

I’ve added parameters for the collection view and the layout to be consistent with how Apple defined theirs. The idea is that the same object might act as a delegate for multiple collection views or layouts, and we might need to resolve between them.

In ViewController.h, add #import "SpringboardLayout.h" and declare that the controller adopts our protocol, by appending <SpringboardLayoutDelegate> at the end of the @interface statement, so that it reads:

In ViewController.m, implement the required method:

Admittedly, all we’re doing is passing the value of the boolean mode variable to the layout, but in a larger project, it could make a lot of difference in terms of code reuse and maintainability.

In SpringboardLayout, insert this convenience method:

Note that we want to reuse the same delegate object (which is a property on the collection view class) rather than have to create and set a new one. So we first check whether the delegate conforms to our protocol and if so, we send it the appropriate message. By default, the UICollectionViewController class (and hence our subclass ViewController) is its collection view’s delegate and data source. And since we announced that ViewController conforms to the SpringboardLayoutDelegate, that’s why the if condition in the above method will be true.

Now how do we percolate this information down to the cells, to get them to reveal or hide the close button, and start/stop the animation?

Since our SpringboardLayout instance is responsible for configuring cell properties that have to do with visual presentation and layout of the cells, it ought to be responsible for calling the appropriate changes on the cells when our deletion mode is activated or deactivated. How exactly? This is part of the collection view architecture that we deferred discussion of, earlier.

Apple’s collection view architecture introduces yet another class, called UICollectionViewLayoutAttributes, that vectors information from the layout to the cell. Attribute instances are created for each cell, and these can very well be configured to depend on some property of the state of our app. In our particular case, think normal state vs. deletion state. The cells receive these attributes and configure themselves accordingly.

While at first glance it might seem that this architecture inserts an unnecessary layer of indirection, it’s actually quite smart. In letting the attributes class mediate the communication between the layout and the cells, we could, say, create different subclasses of cells for the same interface, that interpret the attributes differently, leading to different visual behaviours.

The UICollectionViewLayoutAttribute class contains properties that correspond directly with many of the cell’s visual properties (like opacity, transform, etc.), but we’ll need to extend it so that we can define an attribute that conveys whether the app is in deletion mode or not. Let’s do that now.

Create a new subclass of UICollectionViewLayoutAttributes called SpringboardLayoutAttributes. Replace the contents of the *.h and *.m files with the following:

We’ve introduced a boolean property that defines whether we want the delete button to be hidden or not. The method -copyWithZone: (a method of NSObject) needs to be overridden so that the newly added property in the subclass is taken into account when an attributes object is copied.

Uncomment the import statement #import "SpringboardLayoutAttributes.h" and insert the following methods that inform the layout of the exact class of the attributes (our custom SpringboardLayoutAttributes class) and the methods that helps it create cell attributes keeping in consideration whether deletion mode is active or not:

The final step is to override a method inherited by our Icon class that lets it configure its instance in accordance with the attributes relayed to it.

In Cell.m insert the following method at the indicated spot:

Now all that’s left to do is to set up our gesture recognizers to activate and deactivate the deletion mode.

In ViewController.h, modify the @interface statement to declare that the view controller class conforms to the UIGestureRecognizerDelegate class:

In the ViewController.m class, insert the following snippet in the -viewDidLoad: method at the indicated place:

Insert the action methods for the gesture recognizers:

Observe our use of the very convenient -indexPathForItemAtPoint: method in UICollectionView that lets us check whether our long press or tap falls within the bounds of a cell item or not. Also, observe the very key message * [layout invalidateLayout]*. This signals the occurrence of some event that requires the interface be rebuilt. In this event, it is the activation or deactivation of the deletion mode. New attributes are generated by the SpringboardLayout class for all the visible cells (with the deletion mode property having been toggled), and cells apply these attributes to themselves.

Build the project, and check out the cool, animated deletion mode you just built! Long-pressing on any icon activates it, and tapping anywhere in the background disables it, just like in the iOS home screen.


Where To Go From Here?

I would recommend that you read The Collection View Programming Guide for iOS and watch sessions 205 and 219 (introductory and advanced collection views, respectively) from WWDC 2012. The code for some of the examples presented in these talks are available in the session code too, so you can play around with them.

You might like to extend the example we’ve built so far to endow it with more Springboard-like behaviour, such as making the icons fly into position when the app loads for the first time, or enable dragging of icons and the ability to swap their positions. You might like to peruse the API for the different classes in the collection view framework, as there are a lot of methods that we haven’t touched upon in this tutorial, that you might require or be able to make good use of.

I hope this tutorial has given you a solid basis to get started with your own collection view based app. Thanks for reading!

Advertisement