An iPad iOS 6 Storyboard-based Collection View Tutorial
Previous | Table of Contents | Next |
An Overview of iPad iOS 6 Collection View and Flow Layout | Subclassing and Extending the iOS 6 Collection View Flow Layout |
Learn SwiftUI and take your iOS Development to the Next Level |
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.
Creating the Collection View Example Project
Launch Xcode and create a new project by selecting the options to create a new iPad iOS application based on the Single View Application template. Enter CollectionDemo as the product name and class prefix, set the device to iPad and select the Use Storyboards and Use Automatic Reference Counting options if they are not already selected.
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 CollectionDemoViewController.m and CollectionDemoViewController.h files in the project navigator and pressing the keyboard delete key. When prompted, click the button to move the files to trash.
Next, select the MainStoryboard.storyboard file and, in the storyboard canvas, select the view controller so that it is highlighted in blue (click either on the toolbar beneath the view or the status bar containing the battery indicator to select the view controller as opposed to the view) and hit the keyboard delete key to remove the controller.
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 iOS Cocoa Touch from the left hand panel, followed by Objective-C 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 off and that the option to target the iPad is on before clicking Next. Choose a location for the files and then click Create.
The project now has two new files named MyCollectionViewController.m and MyCollectionViewController.h that represent 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 MainStoryboard.storyboard file and drag and drop a Collection View Controller object from the Object Library panel onto the storyboard canvas as illustrated in Figure 52-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 located in the top left hand corner of the collection view).
Figure 52-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 52-2) from the generic UICollectionViewController class to the newly added MyCollectionViewController class.
Figure 52-2
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 iOS Cocoa Touch from the left hand panel, followed by Objective-C 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 MainStoryboard.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 be 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 52-3) and enter MyCell as the reuse identifier.
Figure 52-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 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 52-4.
Figure 52-4
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.h file then Ctrl-click and drag from the Image View object to a position immediately beneath the @interface line. Release the line and, in the resulting connection panel, establish an outlet connection named imageView. On completion of the connection, select MyCollectionViewCell.h in the project navigator and verify that it reads as follows:
#import <UIKit/UIKit.h> @interface MyCollectionViewCell : UICollectionViewCell @property (strong, nonatomic) IBOutlet UIImageView *imageView; @end
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. A Zip file containing the images can be downloaded from the following link:
http://www.ebookfrenzy.com/code/carImagesMedium.zip
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.h file and declare an array into which will be stored the image file names:
#import <UIKit/UIKit.h> @interface MyCollectionViewController : UICollectionViewController @property (strong, nonatomic) NSMutableArray *carImages; @end
Finally, edit the MyCollectionViewController.m file and modify the viewDidLoad method to initialize the array with the names of the car image files:
- (void)viewDidLoad { [super viewDidLoad]; _carImages = [@[@"chevy_volt.jpg", @"bmw_mini.jpg", @"range_rover.jpg", @"smart_fortwo.jpg", @"toyota_highlander.jpg", @"toyota_venza.jpg", @"volvo_s60.jpg", @"vw_cc.jpg", @"ford_explorer.jpg", @"nissan_gtr.jpg", @"honda.jpg", @"jeep_overland.jpg"] mutableCopy]; }
Note that we are using Modern Objective-C syntax to initialize the array. As outlined in the chapter entitled The Basics of Modern Objective-C, this approach returns a non-mutable array by default. Later in the chapter we will be implementing code to remove items from the array, so need to call the mutableCopy method of the array to make sure we can modify the array later.
Implementing the Data Source
As outlined in the chapter entitled An Overview of iOS 6 Collection Views 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 in 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 52-5.
Figure 52-5
The next step is to declare which protocols the MyCollectionViewController class is going to implement. Since the class will also need to work with the MyCollectionViewCell class, now is also a good opportunity to import the header file for this class. Select the MyCollectionViewController.h file and modify it so that it reads as outlined in the following listing:
#import <UIKit/UIKit.h> #import "MyCollectionViewCell.h" @interface MyCollectionViewController : UICollectionViewController <UICollectionViewDataSource, UICollectionViewDelegate> @property (strong, nonatomic) NSMutableArray *carImages; @end
A number of data source methods will now be implemented 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 select the MyCollectionViewController.m file and implement the numberOfSectionsInCollectionView: method to return a value of 1.
#pragma mark - #pragma mark UICollectionViewDataSource -(NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView { 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:
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 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:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { MyCollectionViewCell *myCell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyCell" forIndexPath:indexPath]; UIImage *image; int row = [indexPath row]; image = [UIImage imageNamed:_carImages[row]]; myCell.imageView.image = image; return myCell; }
Testing the Application
Compile and run the application, either on a physical iPad device or using the iOS Simulator. Once loaded, the collection view will appear as illustrated in Figure 52 6. 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.
Figure 52-6
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.m, implement this method to identify the size of the current image and return the result to the collection view:
#pragma mark - #pragma mark UICollectionViewFlowLayoutDelegate -(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { UIImage *image; int row = [indexPath row]; image = [UIImage imageNamed:_carImages[row]]; return image.size; }
Run the application once again and note that, as shown in Figure 52-7, the images are now displayed at their original sizes.
Figure 52-7
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.m file, implement the didSelectItemAtIndexPath: delegate method as follows:
#pragma mark - #pragma mark UICollectionViewDelegate -(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewFlowLayout *myLayout = [[UICollectionViewFlowLayout alloc]init]; myLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; [self.collectionView setCollectionViewLayout:myLayout animated:YES]; }
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 52-8, for example shows the collection view in landscape orientation with horizontal scrolling enabled.
Figure 52-8
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 MainStoryboard.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 52-9.
Figure 52-9
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 auto layout lines appear. 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 Attribute Inspector and change the Color property to white. Once completed, the layout should now resemble that of Figure 52-10.
Figure 52-10
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 Objective-C 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.h. If a different file is displayed, click on the Manual entries or the Tuxedo image located across the top of the Assistant Editor panel and select Manual -> CollectionDemo -> MySupplementaryView.h:
Figure 52-11
Once MySupplementaryView.h is 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/UIKit.h> @interface MySupplementaryView : UICollectionReusableView @property (strong, nonatomic) IBOutlet UILabel *headerLabel; @end
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 also use a dequeuing mechanism similar to cells. For this example, implement the viewForSupplementaryElementOfKind: as follows in the MyCollectionViewController.m file:
-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { MySupplementaryView *header = nil; if ([kind isEqual:UICollectionElementKindSectionHeader]) { header = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"MyHeader" forIndexPath:indexPath]; header.headerLabel.text = @"Car Image Gallery"; } return header; }
Next, select the MyCollectionViewController.h file and import the header file for the new MySupplementaryView class:
#import <UIKit/UIKit.h> #import "MyCollectionViewCell.h" #import "MySupplementaryView.h" @interface MyCollectionViewController : UICollectionViewController <UICollectionViewDataSource, UICollectionViewDelegate> @property (strong, nonatomic) NSMutableArray *carImages; @end
Compile and run the application once again and note that the header supplementary view is now visible in the collection view.
Deleting Collection View Items
The last task in this tutorial is to implement functionality to allow items to be removed from the data model and the collection view. This is a two step process, the first step being the removal of the item from the data model (in this instance the carItems array). Having removed the item from the data model, the next step is to remove it from the collection view. This is achieved by passing through an array of index path objects for the items to be removed to the deleteItemsAtIndexPaths: method of the collection view object. For the purposes of this example, we will re-purpose the didSelectItemAtIndexPath: delegate method so that instead of changing the scroll direction it now causes the selected item to be deleted. Select the MyCollectionViewController.m file, locate the didSelectItemAtIndexPath: method and modify it so that it reads as outlined in the following listing:
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { int row = [indexPath row]; [_carImages removeObjectAtIndex:row]; NSArray *deletions = @[indexPath]; [self.collectionView deleteItemsAtIndexPaths:deletions]; }
Compile and run the application one final time and note that items are deleted from the collection view as they are selected. Note also how the collection view animates the re-arrangement of the remaining cells to fill the gap left as a result of a deletion.
Summary
The previous chapter (An Overview of iPad iOS 6 Collection View and Flow Layout) covered a considerable amount of ground in terms of the theory behind collection views in iOS 6. 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 |
Previous | Table of Contents | Next |
An Overview of iPad iOS 6 Collection View and Flow Layout | Subclassing and Extending the iOS 6 Collection View Flow Layout |