A Swift iOS 8 Storyboard-based Collection View Tutorial

From Techotopia
Revision as of 14:27, 5 May 2016 by Neil (Talk | contribs) (Text replacement - "<table border="0" cellspacing="0" width="100%">" to "<table border="0" cellspacing="0">")

Jump to: navigation, search
PreviousTable of ContentsNext
An Overview of iOS 8 Collection View and Flow LayoutSubclassing and Extending the iOS 8 Collection View Flow Layout in Swift


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


The primary goal of this chapter is to demonstrate, in a tutorial format, the steps involved in implementing a collection view based application user interface and, in doing so, serve to re-enforce the collection view concepts outlined in the previous chapter. By far the most productive way to implement collection views (and the approach used in this tutorial) is to take advantage of the Storyboard feature of Xcode and, in particular, the collection view cell and supplementary view prototyping options of Interface Builder.


Contents


Creating the Collection View Example Project

Launch Xcode and create a new project by selecting the options to create a new iOS application based on the Single View Application template. Enter CollectionDemo as the product name, choose Swift as the programming language and set the device menu to Universal.

Removing the Template View Controller

Based on the template selection made when the project was set up, Xcode has created a generic UIViewController based subclass for the application. For the purposes of this tutorial, however, this needs to be replaced with a UICollectionViewController subclass. Begin, therefore, by selecting the ViewController.swift file in the project navigator and pressing the keyboard delete key. When prompted, click the button to move the file to trash.

Next, select the Main.storyboard file and, in the storyboard canvas, select the view controller so that it is outlined in blue (the view controller is selected by clicking on the toolbar area at the top of the layout view) and tap the keyboard delete key to remove the controller and leave a blank storyboard.


Adding a Collection View Controller to the Storyboard

The first element that needs to be added to the project is a UICollectionViewController subclass. To add this, select the Xcode File -> New -> File… menu option. In the resulting panel, select Source listed under iOS in the left hand panel, followed by Cocoa Touch Class in the main panel before clicking Next.

On the subsequent screen, name the new class MyCollectionViewController and, from the Subclass of drop down menu, choose UICollectionViewController. Verify that the option to create an XIB file is switched off before clicking Next. Choose a location for the files and then click Create.

The project now has a new file named MyCollectionViewController.swift which represents a new class named MyCollectionViewController which is itself a subclass of UICollectionViewController.

The next step is to add a UICollectionViewController instance to the storyboard and then associate it with the newly created class. Select the Main.storyboard file and drag and drop a Collection View Controller object from the Object Library panel onto the storyboard canvas as illustrated in Figure 55-1.

Note that the UICollectionViewController added to the storyboard has also brought with it a UICollectionView instance (indicated by the black background) and a prototype cell (represented by the white square outline located in the top left hand corner of the collection view).


Ios 8 collection view ui.png

Figure 55-1


With the new view controller selected in the storyboard, display the Identity Inspector either by selecting the toolbar item in the Utilities panel or via the View -> Utilities -> Show Identity Inspector menu option and change the Class setting (Figure 55-2) from the generic UICollectionViewController class to the newly added MyCollectionViewController class.


Ios 8 changing collection view class.png

Figure 55-2


With the view controller scene still selected, display the Attributes Inspector and enable the Is Initial View Controller option so that the view controller is loaded when the app starts.

Adding the Collection View Cell Class to the Project

With a subclass of UICollectionViewController added to the project, a new class must now be added which subclasses UICollectionViewCell.

Once again, select the Xcode File -> New -> File… menu option. In the resulting panel, select the iOS Source option from the left hand panel, followed by Cocoa Touch Class in the main panel before clicking Next.

On the subsequent screen, name the new class MyCollectionViewCell and from the Subclass of drop down menu choose UICollectionViewCell. Click Next, choose a location for the files and then click Create.

Return to the Main.storyboard file and select the white square in the collection view. This is the prototype cell for the collection view and needs to be assigned a reuse identifier and associated with the new class. With the cell selected, open the Identity Inspector panel and change the Class to MyCollectionViewCell. Remaining in the Utilities panel, display the Attributes Inspector (Figure 55-3) and enter MyCell as the reuse identifier.


Ios 8 change collection cell class.png

Figure 55-3

Designing the Cell Prototype

With the basic collection view classes implemented and associated with the storyboard objects, it is now time to design the cell. This is, quite simply, a matter of dragging and dropping items from the Object Library onto the prototype cell in the storyboard view. Additionally the size of the cell may be modified by selecting it and using the resulting resize handles. The exact design of the cell is entirely dependent on what is to be displayed. For the purposes of this example, however, each cell is simply going to display an image.

Begin by resizing the cell to a slightly larger size, locating the Image View object in the Object Library panel and dragging and dropping it into the prototype cell as shown in Figure 55-4.


Ios 8 collection view image.png

Figure 55-4


With the Image View object selected in the storyboard scene, select the Pin menu from the toolbar located in the lower right hand corner of the storyboard panel and set up Spacing to nearest neighbor constraints on all four sides of the view with the Constrain to margins option turned off as illustrated in Figure 55-5:


Ios 8 collection view image constraints.png

Figure 55-5


Once configured, click on the Add 4 Constraints button to establish the constraints.

Since it will be necessary to assign an image when a cell is configured, an outlet to the Image View will be needed. Display the Assistant Editor, make sure it is listing the code for the MyCollectionViewCell.swift file then Ctrl-click and drag from the Image View object to a position immediately beneath the class declaration line. Release the line and, in the resulting connection panel, establish an outlet connection named imageView. On completion of the connection, select MyCollectionViewCell.swift in the project navigator and verify that it reads as follows:

import UIKit

class MyCollectionViewCell: UICollectionViewCell {

    @IBOutlet weak var imageView: UIImageView!
}

With the connection established, the prototype cell implementation is complete.

Implementing the Data Model

The data model for this example application is going to consist of a set of images, each of which will be displayed in a cell within the collection view. The first step in creating this model is to load the images into the project. These images can be found in the carImagesSmall folder of the sample code archive which is downloadable from the following URL:

http://www.ebookfrenzy.com/web/ios8

Once downloaded, unzip the archive and drag and drop the 12 image files onto the Supporting Files section of the Xcode project navigator panel. Next, select the MyCollectionViewController.swift file and declare an array into which will be stored the image file names. Also change the reuseIdentifier constant to match the identifier assigned to the cell in Interface Builder:

import UIKit

let reuseIdentifier = "MyCell"

class MyCollectionViewController: UICollectionViewController {

    var carImages = [String]()
.
.
.
}

Finally, edit the MyCollectionViewController.swift file and modify the viewDidLoad method to initialize the array with the names of the car image files. Also comment out or delete the following line so that it is no longer executed when the application runs. As outlined in the previous chapter, it is not necessary to register cell classes when using Storyboard prototypes:

self.collectionView.registerClass(UICollectionViewCell.self, 
          forCellWithReuseIdentifier: reuseIdentifier)

The completed viewDidLoad method should read as follows:

override func viewDidLoad() {
    super.viewDidLoad()

    // Uncomment the following line to preserve selection between presentations
    // self.clearsSelectionOnViewWillAppear = false

    // Do any additional setup after loading the view.
    carImages = ["chevy_small.jpg",
                "mini_small.jpg",
                "rover_small.jpg",
                "smart_small.jpg",
                "highlander_small.jpg",
                "venza_small.jpg",
                "volvo_small.jpg",
                "vw_small.jpg",
                "ford_small.jpg",
                "nissan_small.jpg",
                "honda_small.jpg",
                "jeep_small.jpg"]
}

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Implementing the Data Source

As outlined in the chapter entitled An Overview of iOS 8 Collection View and Flow Layout, a collection view needs a data source and a delegate in order to provide all the functionality it is capable of providing. By default, Xcode has designated the MyCollectionViewController class as both the delegate and data source for the UICollectionView in the user interface. To verify this, select the black background of the collection view controller in the storyboard to select the UICollectionView subclass instance and display the Connections Inspector (select the far right item at the top of the Utilities panel or use the View -> Utilities -> Show Connection Inspector menu option). Assuming that the connections have been made, the Outlets section of the panel will be configured as shown in Figure 55-6.


Ios 8 collection view delegate.png

Figure 55-6


The next step is to declare which protocols the MyCollectionViewController class is going to implement. Select the MyCollectionViewController.swift file and modify it so that it reads as outlined in the following listing:

import UIKit

let reuseIdentifier = "MyCell"

class MyCollectionViewController: UICollectionViewController, 
     UICollectionViewDataSource, UICollectionViewDelegate {

    var carImages = [String]()

A number of data source methods will now need to be modified to conform with the UICollectionViewDataSource protocol. The first lets the collection view know how many sections are to be displayed. For the purposes of this example there is only going to be one section, so locate the numberOfSectionsInCollectionView method generated by Xcode and modify it to return a value of 1.

override func numberOfSectionsInCollectionView(collectionView: 
		UICollectionView!) -> Int {
    return 1
}

The next method is called by the collection view to identify the number of items that are to be displayed in each section. In this case, the sole collection view section will be required to display a cell for each element in the carImages array. Locate the template method and modify it as follows:

override func collectionView(collectionView: UICollectionView!, 
		numberOfItemsInSection section: Int) -> Int {
    return carImages.count
}

The cellForItemAtIndexPath method will be called by the collection view in order to obtain cells configured based on the indexPath value passed to the method. This method will request a cell object from the reuse queue and then set the image on the Image View object which was configured in the cell prototype earlier in this chapter, using the index path row as the index into the carImages array:

override func collectionView(collectionView: UICollectionView!, 
	cellForItemAtIndexPath indexPath: NSIndexPath!) -> 
	     UICollectionViewCell! {

    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier,
         forIndexPath: indexPath) as !MyCollectionViewCell

    // Configure the cell
    let image = UIImage(named: carImages[indexPath.row])
    cell.imageView.image = image

    return cell
}

Testing the Application

Compile and run the application, either on a physical iPhone device or using the iOS Simulator. Once loaded, the collection view will appear as illustrated in Figure 55 7. Clearly, each cell has been displayed at a fixed size causing the images to be compressed to fit into the containing cell. In order to improve the visual experience, some work is clearly still needed.


Ios 8 collection view running.png

Figure 55-7

Setting Sizes for Cell Items

When the prototype cell was designed, it was set to a specific size. Unless additional steps are taken, each cell within the collection view will appear at that size. This means that images are not displayed at their original size. In actual fact, all of the car images differ in size from each other. What is needed is a way to set the size of each cell based on the size of the content it is required to display (in this instance the dimensions of the corresponding image). As outlined in the previous chapter, if a method named sizeForItemAtIndexPath is implemented in the UICollectionViewFlowLayoutDelegate protocol class (which by default is the same class as the UICollectionViewDelegate delegate), it will be called for each cell to request size information. Clearly, by implementing this method it will be possible to have each image displayed at its own size. Remaining in MyCollectionViewController.swift, implement this method to identify the size of the current image and return the result to the collection view:

// MARK: UICollectionViewFlowLayoutDelegate

func collectionView(collectionView: UICollectionView,
    layout collectionViewLayout: UICollectionViewLayout,
    sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {

    let image = UIImage(named: carImages[indexPath.row])
    return image!.size
}

Run the application once again and note that, as shown in Figure 55-8, the images are now displayed at their original sizes.


Ios 8 collectionview running resized.png

Figure 55-8

Changing Scroll Direction

As currently configured, the flow layout object assigned to the collection view is in vertical scrolling mode. As a demonstration of both one of the delegate methods for handling user interaction and the effects of horizontal scrolling, the example will now be extended to switch to horizontal scrolling when any cell in the view is selected.

When making changes to the flow layout assigned to a collection view, it is not possible to directly change properties on the layout object. Instead, and as illustrated in the following code, a new layout object must be created, configured and then set as the current layout object for the collection view. Within the MyCollectionViewController.swift file, implement the didSelectItemAtIndexPath delegate method as follows:

// MARK: UICollectionViewDelegate

override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {

    let myLayout = UICollectionViewFlowLayout()

    myLayout.scrollDirection = 
		UICollectionViewScrollDirection.Horizontal

    self.collectionView.setCollectionViewLayout(myLayout, 
				animated: true)
}

Compile and run the application and select an image once the collection view appears. Notice how the layout smoothly animates the transition from vertical to horizontal scrolling. Note also that the layout adjusts automatically when the orientation of the device is rotated. Figure 55-9, for example shows the collection view in landscape orientation with horizontal scrolling enabled.


Ios 8 collectionview horizontal.png

Figure 55-9


Implementing a Supplementary View

The next task in this tutorial is to demonstrate the implementation of supplementary views in the form of a header for the car image gallery. The first step is to ask Interface Builder to add a prototype header supplementary view to the UICollectionView. To do this, select the Main.storyboard file and click on the black background of the collection view controller canvas representing the UICollectionView object. Display the Attributes Inspector in the Utilities panel, locate the Accessories section listed under Collection View and set the Section Header check box as illustrated in Figure 55-10.


Ios 8 collectionview header.png

Figure 55-10


Once the header section has been enabled, a header prototype object will have been added to the storyboard view canvas. As with the prototype cell, this header can be configured using any combination of view objects from the Object Library panel. For this example, drag a Label object onto the prototype header and position it so the horizontal and vertical center guidelines appear. Using the Auto Layout Align menu, set constraints on the label so that it is positioned in the horizontal and vertical center of the containing view as shown in Figure 55-11:


Ios 8 collection view header constraints.png

Figure 55-11


By default, labels have a black foreground so the text in the label will not be visible until a different color is selected. With the label still selected, move to the Attributes Inspector and change the Color property to white. Once completed, the layout should now resemble that of Figure 55-12.


Ios 8 collection demo header ui.png

Figure 55-12


With a header prototype added to the storyboard, a new class needs to be added to the project to serve as the class for the header. Select File -> New -> File… and add a new iOS Cocoa Touch class named MySupplementaryView subclassed from UICollectionReusableView. Select the header in the storyboard and, using the Identity Inspector, change the class from UICollectionReusableView to MySupplementaryView.

As with cells, supplementary views are reused, so select the Attributes Inspector in the Utilities panel and enter MyHeader as the reuse identifier.

The text displayed on the header will be set within a data source method of the view controller. As such, an outlet to the label will be needed. Display the Assistant Editor and make sure that it is displaying the code for MySupplementaryView.swift. With the MySupplementaryView.swift file displayed in the Assistant Editor, establish an outlet connection from the label in the header and name it headerLabel. On completion of the connection, the content of the file should read as follows:

import UIKit

class MySupplementaryView: UICollectionReusableView {

    @IBOutlet weak var headerLabel: UILabel!
}

Implementing the Supplementary View Protocol Methods

In order for the supplementary header view to work, a data source method needs to be implemented. When the collection view is ready to display a supplementary view it will call the viewForSupplementaryElementOfKind method of the data source and expect, in return, a configured object ready to be displayed. Passed through as an argument to this method is a value indicating whether this is a header or footer which can be checked by the code to return the appropriate object. Note also that supplementary views use a dequeuing mechanism similar to cells. For this example, implement the viewForSupplementaryElementOfKind method as follows in the MyCollectionViewController.swift file:

override func collectionView(collectionView: UICollectionView,    
    viewForSupplementaryElementOfKind kind: String, 
    atIndexPath indexPath: NSIndexPath) 
              -> UICollectionReusableView {

    var header: MySupplementaryView?

    if kind == UICollectionElementKindSectionHeader {
        header = 
          collectionView.dequeueReusableSupplementaryViewOfKind(kind, 
          withReuseIdentifier: "MyHeader", forIndexPath: indexPath) 
             as? MySupplementaryView

        header?.headerLabel.text = "Car Image Gallery"
    }
        return header!
}

Compile and run the application once again and note that the header supplementary view is now visible in the collection view.

Summary

The previous chapter covered a considerable amount of ground in terms of the theory behind collection views in iOS 8. This chapter has put much of this theory into practice through the implementation of an example application that uses a collection view to display a gallery of images.


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book



PreviousTable of ContentsNext
An Overview of iOS 8 Collection View and Flow LayoutSubclassing and Extending the iOS 8 Collection View Flow Layout in Swift