Swift Development with Cocoa (2015)

Chapter 12. Table Views and Collection Views

One of the most common tasks for any app, regardless of platform, is displaying lists or collections of data. iOS and OS X provide a number of tools for viewing data, and in this chapter you’ll learn how to use them.

Both iOS and OS X feature table views and collection views. A table view is designed to provide a list of data, while a collection view is designed to show a grid of data. Both table views and collection views can be customized to provide different layouts.

Table views are used all over OS X—Finder and iTunes both use it to show lists of files and songs. Table views are used even more heavily in iOS—any time you see a vertically scrolling list, such as the list of messages in Messages or the options in Settings, you’re seeing a table view.

Collection views are used a little less frequently, as they’re a newer addition to both platforms. Collection views can be seen (again) in Finder and iTunes, as well as in Launchpad. On the iPad, a collection view appears in the Clock application, which was added in iOS 6.

Data Sources and Delegates

Despite their differences in layout, table and collection views have very similar APIs. When a data display view prepares to show content, it has to know the answers to at least two questions:

§  How many items am I showing?

§  For each item, what do I need to do to display it?

These questions are asked of the view’s data source, which is an object that conforms to the table view’s data source protocol. The data source protocol differs based on the type of view that you’re using.

There are other questions that a data view may need to know the answer to, including “How tall should each row in the list be?” and “How many sections should the list contain?” These questions are also answered by the data source, but the view can fall back to some default values in case the data source isn’t able to provide this information.

Sometimes displaying information is all you want, but your application usually needs to respond to the user interacting with the view’s content. The specific things that the user can do vary depending on the platform, the kind of data view, and how you’ve configured the view. Some possible interactions include:

§  Clicking (or tapping) on an item

§  Rearranging content

§  Deleting or modifying content

These actions are sent by the view to its delegate.

Table Views

Table views are designed for showing lists of information. On OS X, a table view shows data with multiple columns, which can be rearranged and resized, and is generally used to show data. On iOS, table views only show one column and are useful for any kind of vertically scrolling list, as seen in the Settings application.

UITableView on iOS

Table views are implemented on iOS using the UITableView class. This is one of the most versatile view classes on iOS: with it, you can create interfaces that range from simple lists of data to complex, multipart, scalable interfaces.

On iOS, the term “table view” is somewhat of a misnomer. The word “table” usually brings to mind a grid with multiple rows and columns, but on iOS the table view is actually a single column with multiple rows. The reason for this is that the size of the iPhone’s screen is too narrow for more than one column to make sense, but the API design for UITableViewController was based on NSTableViewController, which we’ll discuss later in this chapter.

NOTE

If you want to create a multiple-column table view on iOS, you either need to build it yourself, or use a UICollectionView (which we discuss in Collection Views).

Table views on iOS present a scrolling list of table view cells, which are views that can contain any data you like. UITableView is designed for speed: one of the most common gestures that the user performs on an iOS device is to flick a finger up and down a scrolling list, which means that the application needs to be able to animate the scrolling of that list at high frame rates (ideally, 60 frames per second, which is the maximum frame rate of iOS).

Sections and Rows

Table views can be divided into multiple sections, each of which contains one or more rows. Sections allow you to divide your content in a manner that makes sense to you. For example, the Contacts application uses a table view that divides rows by surname, and the Settings application uses a table view that divides rows into categories.

Because table views are divided into sections, specific locations in the table view are identified not by row, but by index path. An index path is nothing more complex than a section number and a row number, and is represented using the NSIndexPath class:

let indexPath = NSIndexPath(forRow: 2, inSection: 1)

(Note that you don’t usually create NSIndexPath’s yourself—this example just shows how they’re composed.)

Let’s imagine that we’ve got a table view that’s divided into two sections: the first section has two rows, and the second section has three (Figure 12-1).

A table view, divided into sections

Figure 12-1. A table view, divided into sections

Using index paths, you can refer to the very first cell as section 0, row 0. The second cell is section 0, row 1, and the third cell is section 1, row 0. This allows row numbers to be independent of their sections, which can be very handy indeed.

Table View Controllers

If you add a UITableView to an interface without doing any additional work, you’ll see an empty list. By default, UITableViews rely on a data source object to provide them with information on what content to show.

Any object can be a data source for a UITableView; the only requirement is that it must conform to the UITableViewDatasource protocol (for more information on protocols, see Protocols).

The object is almost always a UIViewController, and almost always the view controller of the view that contains the table view. There’s nothing stopping you from doing it differently, though.

The two critical methods that the UITableViewDatasource protocol defines are:

func tableView(tableView: UITableView!,

    numberOfRowsInSection section: Int) -> Int

func tableView(tableView: UITableView!,

        cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!

The first, numberOfRowsInSection, returns the number of rows in the specified table section (see Sections and Rows). The second, cellForRowAtIndexPath, returns a UITableViewCell for the specified index path.

For example, here’s how to indicate that every section in the table has two rows:

func tableView(tableView: UITableView!,

    numberOfRowsInSection section: Int) -> Int {

    return 2

}

This method is the easier of the two. Here’s an example of an implementation of tableView(_, cellForRowAtIndexPath:) (we’ll talk about exactly what it does in the next section):

    override func tableView(tableView: UITableView,

    cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("StringCell",

    forIndexPath: indexPath) as UITableViewCell

        cell.textLabel.text = "Hello!"

        return cell

    }

Table View Cells

A table view cell represents a single item in the table view. Table view cells are UITableViewCells, a subclass of UIView. Just like any other UIView, table view cells can have any other UIViews included as subviews.

When the table view needs to show data, it needs the answer to two questions: how many rows are there to show, and what should be shown for each row. The first question is answered by the numberOfRowsInSection method; the second is answered by tableView(_, cellForRowAtIndexPath:).

cellForRowAtIndexPath is called for every visible row in the table view as it comes into view. This last part is important because it enables the table view to not have to worry about displaying content that isn’t visible. If, for example, you have a table view that contains 1,000 objects, fewer than 10 of those objects are likely to be visible. Because it makes no sense for the table view to attempt to display table view cells for rows that may never be shown, tableView(_, cellForRowAtIndexPath:) is called only as a cell is about to come onto the screen.

tableView(_, cellForRowAtIndexPath:) is responsible for returning a configured UITableViewCell. “Configured,” in this case, means making sure that the table view cell is displaying the right content. That content depends on what the table view is being used for: if you’re making a shopping list application, each table view cell would contain an item in the shopping list.

Cell reuse

As the user scrolls the table view, some items in the list will go off screen while others come on screen. When a table view cell in the list scrolls off screen, it is removed from the table view and placed in a reuse queue. This reuse queue stores UITableViewCell objects that have been created but are not currently visible. When a cell is scrolled into view, the table view retrieves an already created UITableViewCell object from the reuse queue.

The advantage of this method is that the time taken to allocate and set up a new object is completely removed. All memory allocations take time, and if the user is quickly scrolling through a long list, he would see a noticeable pause as each new cell appeared.

UITableViewCell objects are automatically placed into the reuse queue by the table view as the cells scroll off screen; when the table view’s data source receives the tableView(cellForRowAtIndexPath:) message, it fetches a cell from the reuse queue and prepares that, rather than creating an entirely new cell.

A table view can have many different kinds of cells—for example, you might have a table view with two sections that show entirely different cells in each section. However, there’s only one tableView(cellForRowAtIndexPath:) method, which is called for all rows in all sections. To differentiate, you can use the index path that is passed to this method to figure out which section and row the table view wants a cell for.

Anatomy of a UITableViewCell

A table view cell is a UIView, which can contain any additional UIView s that you want to include. In addition to this flexibility, UITableViewCell objects also support a few basic styles, which are similar to the table view cells seen in other parts of iOS. There are four basic table view styles:

Default

A black, bold, left-aligned text label, with an optional image view. As the name suggests, this is the default style for table view cells.

Right Detail

A black, left-aligned text label, with a smaller, blue-colored, right-aligned text label on the righthand side. This cell style can be seen in the Settings application.

Left Detail

A blue, right-aligned label on the lefthand side, with a black, left-aligned label on the righthand side. This cell style can be seen in the Phone and Contacts applications.

Subtitle

A black, left-aligned label, with a smaller, gray label underneath it. This cell style can be seen in the Music application.

The common theme is that all table view cells have at least one primary text label, and optionally a secondary text label and an image view. These views are UILabel and UIImageView objects, and can be accessed through the textLabel, detailTextLabel, and imageView properties of the table view cell.

You can see examples of all four built-in table view cell styles in Figure 12-2.

Preparing table views in Interface Builder

Prior to iOS 5, constructing table views and table view cells was a largely programmatic affair, with developers writing code that manually instantiated and laid out the contents of any nonstandard table view cells in code. This wasn’t a great idea, because layout code can get tricky. So, from iOS 5 onward, table views and their cells can be designed entirely in Interface Builder.

When you add a table view to an interface, you can also create prototype cells. The contents of these cells can be designed by you and completely customized (from changing the colors and fonts to completely changing the layout and providing a custom subclass of UITableViewCell). These prototype cells are marked with a cell identifier, which allows your code to create instances of the prototypes.

The different table view cell styles (from top to bottom: Basic, Right Detail, Left Detail, and Subtitle)

Figure 12-2. The different table view cell styles (from top to bottom: Basic, Right Detail, Left Detail, and Subtitle)

Analyzing tableView(_, cellForRowAtIndexPath:)

With all of this in mind, we can now take a closer look at the tableView(_, cellForRowAtIndexPath:) implementation that we looked at earlier. Here it is again:

    override func tableView(tableView: UITableView,

    cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("StringCell",

    forIndexPath: indexPath) as UITableViewCell  1

        cell.textLabel.text = "Hello!"  2

        return cell  3

    }

The method performs three actions:

1

The table view is asked to dequeue a table view cell that has a cell identifier of StringCell. This causes the table view to either create an instance of the prototype cell that has this identifier, or dequeue one from the reuse queue if a cell with this identifier has been previously created and is not currently visible.

2

The cell’s primary text label is set to display the text “Hello!”

3

Finally, the cell is returned to the table view, which will display it to the user.

Responding to actions

The most common thing that the user does with table view cells is to tap them. When this happens, the table view will contact its delegate and inform it that a cell was selected.

An object must conform to the UITableViewDelegate protocol in order to be a delegate. The table view’s delegate can be different from its data source, but in practice the delegate is the same object as the data source (i.e., the view controller that manages the table view conforms to both the UITableViewDelegate and UITableViewDatasource protocols).

There are several methods that UITableViewDelegate specifies, all of which are optional. The most important and commonly used method is tableView(_, didSelectRowAtIndexPath:), which is called when a row is tapped. This is the delegate’s opportunity to perform an action like moving to another screen:

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath

                            indexPath: NSIndexPath)  {

        println("Selected \(data[indexPath.row])")

    }

Implementing a Table View

To put it all together, we’ll build a simple table view application that displays the contents of an array of NSString objects:

1.    Create a new, single view iOS application named iOSTableView.

2.    Delete the ViewController.swift file. We’ll be creating a replacement for it shortly.

3.    Create and set up the table view. In this example, the view controller will be a UITableViewController, which is a subclass of UIViewController that manages a single UITableView.

Open Main.storyboard and delete the existing view controller. Then drag in a table view controller from the objects library. Select the new view controller, and open the Attributes Inspector. Turn on the Is Initial View Controller checkbox.

4.    Set up the prototype cell. By default, a UITableViewController that’s been dragged into the storyboard contains a single prototype cell. We’re going to configure that to be the cell that we want.

Select the single cell that appears at the top of the table view.

Make sure the Attributes Inspector is open, and change its identifier to StringCell.

Change the cell’s style from Custom to Basic, by using the drop-down menu at the top of the Attributes Inspector.

The table view is fully configured; it’s now time to write the code that will provide the table view with the information it needs:

1.    Create the new table view controller class. Create a new Cocoa Touch class by choosing File→New→File… and selecting Cocoa Touch class. Name it TableViewController and make it a subclass of UITableViewController.

2.    Make the table view controller use the new class. Go back to the storyboard and select the view controller.

Note that clicking on the table view won’t select the view controller—it’ll select the table view. You can select the table view controller itself from the Outline view on the lefthand side of the Interface Builder.

Go to the Identity Inspector and change the class from UITableViewController to TableViewController.

3.    Open TableViewController.swift and add the array of strings.

We can now write the code that drives the table view. First, we need to create an array that contains the strings that will be displayed.

Add the following property to TableViewController:

    var data = ["Once", "upon", "a", "time"]

4.    Make the table view data source return one section. The table view will contain one section, and this section will contain as many rows as there are entries in the data array.

To determine how many sections are present, the table view sends its data source object the numberOfSectionsInTableView message. This method is already implemented in the template code, but returns zero. We just need to change this to return 1.

Find the numberOfSectionsInTableView method in TableViewController.swift and replace it with the following code:

    override func numberOfSectionsInTableView(tableView: UITableView)

        -> Int {

        return 1

    }

5.    Make the table view data source indicate the correct number of rows for the section. We need to tell the table view that the section has as many rows as there are objects in the data array. This is handled by the tableView(_, numberOfRowsInSection:) method.

Find the tableView(_, numberOfRowsInSection:) method in TableViewController.swift and replace it with the following code:

    override func tableView(tableView: UITableView,

        numberOfRowsInSection section: Int) -> Int {

        return data.count

    }

6.    Implement the tableView(_, cellForRowAtIndexPath:) method. We need to prepare the table view cells for each of the rows that the table view will ask for.

Find the tableView(_, cellForRowAtIndexPath:) method in TableViewController.swift and replace it with the following code:

    override func tableView(tableView: UITableView,

        cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("StringCell",

                            forIndexPath: indexPath) as UITableViewCell

        let string = data[indexPath.row]

        cell.textLabel.text = string

        return cell

    }

7.    Implement the tableView(_, didSelectRowAtIndexPath:) method. Finally, we’ll make the code log the string corresponding to the text that was selected.

Add the tableView(_, didSelectRowAtIndexPath:) method in TableViewController.swift:

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath

                            indexPath: NSIndexPath)  {

        println("Selected \(data[indexPath.row])")

    }

NSTableView on OS X

The process of displaying tables of data on OS X is slightly more complex than on iOS. Tables on OS X are capable of displaying multiple columns of data, which can be rearranged and resorted by the user. Table views on OS X are instances of the NSTableView class. However, the fundamental idea behind table views on OS X is the same as on iOS—a table view uses a data source object to determine how many rows exist and what content should be shown for each row.

The only significant difference in terms of programming for NSTableView is that the method that returns the content that should be shown for a table view cell needs to take into account both the row number and column for the data.

The method for returning the view that should be shown in a cell is tableView(viewForTableColumn:row:). This method’s parameters are the NSTableView that wants to show content, and the column and row number that are being displayed. The row number is represented as a simple integer, while the table column is an NSTableColumn. This is because columns can be rearranged, and it therefore doesn’t make sense to have “column numbers.” Rather, NSTableColumn objects have identifiers, which are used by your code to figure out what specific piece of information needs to be shown.

To demonstrate how table views work on OS X, we’ll build an application that displays multiple columns of data. This app will display a list of songs, along with their running times.

1.    Create a new Cocoa application called CocoaTableView.

2.    Create the Song class. The first thing we’ll do is create the data source. Each song that the application displays will be an instance of the class Song, which we’ll create ourselves. Each Song object has a title string, as well as a duration represented as an NSTimeInterval (which is just another way of saying float—it’s a typedef defined by Cocoa).

To create the class, go to File→New→File… and choose Cocoa class. Create a new class called Song and make it a subclass of NSObject.

Once it’s been created, open Song.swift and make it look like the following code:

class Song: NSObject {

    var title : String = "A Song"

    var duration : NSTimeInterval = 0.0

}

NOTE

This class only contains properties and no methods—it’s only for storing data.

3.    Next, we’ll make AppDelegate store a list of Song objects. This list will be an NSMutableArray, which is managed by an NSArrayController.

NOTE

This controller won’t be used immediately, but instead will be used as part of the bindings used to drive the table view, later in this chapter.

Open AppDelegate.swift and add the following properties to the class:

    var songs : [Song] = []

    @IBOutlet var songsController: NSArrayController!

4.    Finally, we need to make the object populate this list when it appears.

Add the following method to the AppDelegate class:

    override func awakeFromNib() {

        if self.songs.count == 0 {

            var aSong : Song!

            aSong = Song()

            aSong.title = "Gaeta's Lament"

            aSong.duration = 289

            self.songsController.addObject(aSong)

            aSong = Song()

            aSong.title = "The Signal";

            aSong.duration = 309

            self.songsController.addObject(aSong)

            aSong = Song()

            aSong.title = "Resurrection Hub";

            aSong.duration = 221

            self.songsController.addObject(aSong)

            aSong = Song()

            aSong.title = "The Cult of Baltar";

            aSong.duration = 342

            self.songsController.addObject(aSong)

        }

    }

NOTE

Bonus points for those who get the reference!

We’ll now prepare the interface for the application:

1.    Open MainMenu.xib, and drag an array controller into the outline.

Open the Bindings Inspector, and bind the Content Array to the App Delegate. Set the model key path to self.songs.

Hold down the Control key and drag from the app delegate to the array controller. Choose songsController from the menu that appears.

2.    Create the table view. Open MainMenu.xib and select the window. It’s empty, but we’ll soon fix that.

Drag a table view from the objects library into the window. Make it fill the window.

Select the table header view at the top of the table view. Double-click the first column’s header and rename it Title. Rename the second column header Duration.

3.    We now need to set up the columns to have the correct identifiers, and to use NSViews as their content rather than old-style NSCells (which was the previous method prior to OS X 10.7 Lion).

Select the “Title” table column object in the outline. Switch to the Identity Inspector and set the Restoration ID to “Title.”

Then, select the “Duration” table column in the outline and change its Restoration ID to “Duration.”

Finally, select the table view in the outline and change its content mode from Cell Based to View Based.

4.    Next, we need to set up the table view’s data source and delegate. Control-drag from the table view to the app delegate, and choose “dataSource” from the menu that appears. Then Control-drag from the table view to the app delegate again, and choose “delegate.”

5.    The AppDelegate class needs to conform to the NSTableViewDataSource and NSTableViewDelegate protocols in order to satisfy the compiler.

Open AppDelegate.swift, and make the class conform to two new protocols:

class AppDelegate: NSObject, NSApplicationDelegate,

                   NSTableViewDelegate, NSTableViewDataSource {

6.    Add the numberOfRowsInTableView method to AppDelegate. This method indicates to the table view how many rows should appear:

7.      func numberOfRowsInTableView(tableView: NSTableView!) -> Int {

8.          return self.songs.count

    }

9.    Add the tableView(viewForTableColumn:row:) method to AppDelegate. This method returns an NSView that will appear in the table view cell, based on the row number and column used:

10.    func tableView(tableView: NSTableView!, viewForTableColumn

11.                 tableColumn: NSTableColumn!, row: Int) -> NSView!  {

12. 

13.        let cell =

14.                    tableView.makeViewWithIdentifier(tableColumn.identifier,

15.                        owner: self) as NSTableCellView

16. 

17.        let textField = cell.textField

18.        let song = self.songs[row]

19. 

20.        if tableColumn.identifier == "Title" {

21.            textField?.stringValue = song.title

22.        } else if tableColumn.identifier == "Duration" {

23.            let durationText = NSString(format: "%i:%02i",

24.                                        Int(song.duration) / 60,

25.                                        Int(song.duration) % 60)

26.            textField?.stringValue = durationText

27.        }

28. 

29.        return cell

    }

In this method, the table view is asked to dequeue a reusable view based on the identifier of the table column. This is returned as a NSTableCellView, which will contain the text field that should show the text.

Then, depending on the specific column, the text of the text field is set to either the song’s title or a string representation of the song’s duration.

Finally, run the application. Behold the songs!

Sorting a Table View

When you click a table view header, you’re indicating to the table view that it should re-sort the contents of the table. To do this, the table columns need to know what specific property they’re responsible for showing.

This is implemented by providing sort keys to each of the columns. Sort keys indicate what property should be used for sorting.

To add sort keys, select the “Title” table column in the outline. Open the Attributes Inspector and set the sort key to title. Leave the selector as compare: and the order as Ascending. Then, select the “Duration” table column in the outline, and change the sort key to duration.

When a table column header is clicked, the table view’s data source receives a tableView(sortDescriptorsDidChange:) message. A sort descriptor is an instance of the NSSortDescriptor class, which provides information on how a collection of objects should be sorted.

To sort an array using sort descriptors, you take the array, and use the sort method. This method takes a closure that it uses to work out how a pair of objects is ordered; you can simple take each sort descriptor, and use its compareObject(toObject:) method to work out this ordering.

To implement the tableView(sortDescriptorsDidChange:) method, add the following method to AppDelegate:

    func tableView(tableView: NSTableView!,

                    sortDescriptorsDidChange oldDescriptors: [AnyObject]!)  {

        // Apply each sort descriptor, in reverse order

        for sortDescriptor in tableView.sortDescriptors.reverse()

            as [NSSortDescriptor] {

            songs.sort() {

                (item1, item2) in

                return sortDescriptor.compareObject(item1, toObject: item2)

                    == NSComparisonResult.OrderedAscending

            }

        }

        tableView.reloadData()

    }

Now launch the application. Click one of the headings, and note the table view re-sorting.

NSTableView with Bindings

The NSTableView class is quite straightforward to use with a code-driven data source, but it’s often a lot simpler to use Cocoa bindings (see Chapter 11). So to cap off our coverage of NSTableView, we’re going to adapt the code to use Cocoa bindings.

When using bindings, we bind both the table view and the specific views in each table view cell. The table view is bound to the array controller so that it knows how many rows exist, and the views in the cells are bound to the specific property that should be displayed.

To bind the table view to the array controller:

1.    Select the table view in the outline. Go to the Connections Inspector and remove the dataSource and delegate links.

2.    Go to the Bindings Inspector, and bind the table view’s Content to Array Controller.

3.    Select the text field in the table view cell in the Title column. Bind its value to Table Cell View and set the model key path to objectValue.title. This will make the cell display the title of the Song object that this row is displaying.

4.    Select the text field in the table view cell in the Title column. Bind its value to Table Cell View” and set the model key path to objectValue.durationString. This a method that we’re about to create.

We want to display a human-readable representation of the Song object’s duration property, and the best way to do that is to add a durationString method that formats the underlying NSTimeInterval appropriately. To add this method to the Song class, add the following toSong.swift:

    func durationString() -> String {

        return NSString(format: "%i:%02i", Int(self.duration) / 60,

                            Int(self.duration) % 60)

    }

Now run the application; you can continue to see the songs.

Collection Views

A collection view is a tool for displaying a collection of objects. While table views are great for tabular displays of data, you often want to display a collection of items in a way that isn’t a list.

Collection views exist on both iOS and OS X, though the implementation is better on iOS. In this section, you’ll learn how to use UICollectionView, the iOS class that allows you to display a collection of views.

NOTE

We aren’t covering NSCollectionView, the OS X counterpart to UICollectionView, in this book, mostly because the API is a little cumbersome and also because there aren’t as many use cases for it. If you need more information on NSCollectionView, take a look at the Collection View Programming Guide, included as part of the Xcode developer documentation.

UICollectionView on iOS

UICollectionView lets you present a collection of items in a way that doesn’t require each item to know how it’s being positioned or laid out. UICollectionView behaves rather like UITableView, but it doesn’t just lay content out in a vertical list—rather, it supports customizable layout handlers called layout objects.

The UICollectionView class makes use of a data source and delegate, much like the UITableView and NSTableView classes. The UICollectionView displays a collection of UICollectionViewCell objects, which are UIViews that know how to be laid out in a collection view. Generally, you create subclasses of these cells and fill them with content.

NOTE

By default, a UICollectionView displays its content in a grid-like fashion. However, it doesn’t have to—by creating a UICollectionViewLayout subclass and providing it to the collection view, you can lay out the UICollectionViewCell objects in any way you want. UICollectionViewLayout subclassing is a little beyond the scope of this chapter, but there’s plenty of interesting discussion in the documentation for this class.

To demonstrate collection views in use, we’re going to create an application that displays a collection of numbers in a grid:

1.    Create a single view application for iPad called AwesomeGrid.

2.    Create the collection view controller. Delete the ViewController.swift file. We’ll be replacing it shortly.

Create a new UICollectionViewController subclass by choosing File→New→File… and creating a new Cocoa Touch object named GridViewController. Make it a subclass of UICollectionViewController.

3.    Prepare the collection view. Open Main.storyboard and delete the view controller. Drag in a collection view controller. With the new view controller selected, open the Identity Inspector and change its class from UICollectionViewController to GridViewController.

We’ll now create our own subclass of the UICollectionViewCell class, which will contain a label. Unlike UITableViewCell objects, the UICollectionViewCell doesn’t provide standard styles for cells, as it doesn’t make assumptions about the content your application will be showing.

The actual contents of the UICollectionViewCell will be designed in the Interface Builder.

Create the collection view subclass and use it in the collection view. Create a new UICollectionViewCell subclass by choosing File→New→File… and creating a new Cocoa Touch object named GridCell. Make it a subclass of UICollectionViewCell.

Go back to Main.storyboard and select the collection view cell at the upper left of the collection view. Go to the Identity Inspector and change its class from UICollectionViewCell to GridCell.

Go to the Attributes Inspector and change the collection view item’s identifier to Cell.

Resize the cell to be about twice the size. Drag a label into the cell. Using the Attributes Inspector, make its font larger, and change its color to white. Resize the label to fill the cell and make the text centered. Add constraints to center it horizontally and vertically within the cell.

Open GridCell.swift in the assistant. Control-drag from the label into the GridCell class, and create a new outlet called label.

Having set up the collection view, we can now set up the view controller to display the content. The actual “content” to be displayed will be the numbers from 1 to 200, which will be stored as numbers objects in an array. For each GridCell that the collection view needs to display, the view controller will convert the number to an string and display it in the UILabel.

The first step in this process is to store the array of numbers:

1.    Prepare the data. Open GridViewController.swift.

Add the following property to the GridViewController class:

    var numbers : [Int] = []

Next, replace the viewDidLoad method with the following code (removing all other code):

    override func viewDidLoad() {

        super.viewDidLoad()

        for i in 1...200 {

            numbers.append(i)

        }

    }

2.    Add the methods that indicate the number of items in the collection view. The methods for providing data to a UICollectionView are very similar to those for working with a UITableView: you provide the number of sections, the number of items in each section, and aUICollectionViewCell object for each item.

Add the following methods to GridViewController. If they already exist, replace the code for them:

    override func numberOfSectionsInCollectionView

                    (collectionView: UICollectionView) -> Int {

        return 1

    }

    override func collectionView(collectionView: UICollectionView,

                 numberOfItemsInSection section: Int) -> Int {

        return self.numbers.count

    }

3.    Implement the collectionView(cellForItemAtIndexPath:) method. Displaying a cell in a collection view is just as simple. Because we have already prototyped the GridCell in the Interface Builder, the only thing that needs to happen is for the view controller to prepare the cell when it appears in the collection view.

Add the following method to GridViewController:

override func collectionView(collectionView: UICollectionView,

            cellForItemAtIndexPath indexPath: NSIndexPath)

                                                    -> UICollectionViewCell

{

    let cell =

        collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier,

                            forIndexPath: indexPath) as UICollectionViewCell

    if let gridCell = cell as? GridCell {

        gridCell.label.text = String(self.numbers[indexPath.row])

    }

    return cell

}

Run the application—you should see a scrolling grid of numbers.

Note that when you rotate the iPad (if you’re using the simulator, use the ⌘-← and ⌘-→ keys), the collection view lays itself out correctly.