Skip to content

Building Complex Layouts with Collection Views

rayastar edited this page Feb 1, 2015 · 2 revisions

5.1. Constructing Collection Views

Let’s explore the full-screen scenario first. Follow these steps to create a simple app that displays a full-screen collection view on the screen:

  1. Create Empty Application

  2. Now that you have your project set up, create a new class in your project and call it ViewController. This class has to subclass UICollectionViewController. You won’t need a .xib file for this view controller, so skip that option

  3. Now that you have your project set up, create a new class in your project and call it ViewController. This class has to subclass UICollectionViewController. You won’t need a .xib file for this view controller, so skip that option:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

  /* Instantiate the collection view controller with a nil layout object.
           Note: this will throw an exception, but later we will learn how we can
           create layout objects and provide them to our collection views */
    ViewController *viewController =
    [[ViewController alloc]
     initWithCollectionViewLayout:[self flowLayout]];

    self.window = [[UIWindow alloc] initWithFrame:
                   [[UIScreen mainScreen] bounds]];

    self.window.backgroundColor = [UIColor whiteColor];

    /* Set the collection view as the root view controller of our window */
    self.window.rootViewController = viewController;
    [self.window makeKeyAndVisible];
    return YES;
}

5.2. Assigning a Data Source to a Collection View

The data source of the collection view, like that in table views, is responsible for pro‐ viding enough data to the collection view so that it can render its contents on the screen. The way things are rendered on the screen is not the data source’s job. That is the layout’s job. The cells that the layout displays on your collection view will ultimately be provided by your collection view’s data source. Here are the required methods of the UICollectionViewDataSource protocol that you have to implement in your data source: collectionView:numberOfItemsInSection: This method returns an NSInteger that dictates to the collection view the number of items that should be rendered in the given section. The given section is passed to this method as an integer that represents the zero-based index of that section. This is the same in table views. collectionView:cellForItemAtIndexPath: Your implementation of this method must return an instance of the UICollection ViewCell that represents the cell at a given index path. The UICollectionView Cell class subclasses the UICollectionReusableView class. In fact, any reusable cell given to a collection view to render must either directly or indirectly subclass UICollectionReusableView, as we will see later in this chapter. The index path will be given to you in the cellForItemAtIndexPath parameter of this method. You can query the section and the row indexes of the item from the index path. Let’s go into the collection view controller’s implementation file (ViewController.m) that we created in the Recipe 5.1, and implement the aforementioned collection view data source methods:

#import "ViewController.h"
    @implementation ViewController
    /* For now, we won't return any sections */
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return 0;
}
    /* We don't yet know how we can return cells to the collection view so
     let's return nil for now */
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
return nil;
}
@end

5.3. Providing a Flow Layout to a Collection View

A flow layout can easily be instantiated, but before it can be passed to a collection view, it has to be configured. Here we are going to discuss the various properties that you can tweak on an instance of the UICollectionViewFlowLayout class and how they can affect the rendering of your collection view cells:

minimumLineSpacing A floating point value that dictates to the flow layout the minimum number of points it has to reserve between each row. The layout object may decide to allocate more space in order to make the layout look good, but it must not allocate less. If your collection view is too small for the items to fit into it, your items will be clipped, just like any other view in the iOS SDK.

minimumInteritemSpacing A floating point value to indicate the minimum number of points that the layout should reserve between cells on the same row. Again, this is the minimum number of points, and the layout, depending on the size of the collection view, may decide to increase this number.

itemSize A CGSize that specifies the size of every cell in the collection view.

scrollDirection A value of type UICollectionViewScrollDirection that tells the flow layout how the collection view’s contents have to be scrolled. You can have the contents scroll either vertically or horizontally, but not both. The default value of this property is UICollectionViewScrollDirectionVertical, but you can change it to UICollec tionViewScrollDirectionHorizontal.

sectionInset A value of type UIEdgeInsets that specifies the margins around every section. The margins are basically spaces that will not be occupied by any cells. You can use the UIEdgeInsetsMake function to create these insets, which are made out of top, left, bottom, and right edges, each of type float. Don’t worry if you find this explanation confusing; we will look at an example soon.

Now you are going to modify your app delegate to provide a valid flow layout to our collection view controller:

#import "AppDelegate.h"
#import "ViewController.h"
@implementation AppDelegate

- (UICollectionViewFlowLayout *) flowLayout {
    UICollectionViewFlowLayout *flowLayout =
    [[UICollectionViewFlowLayout alloc] init];
    flowLayout.minimumLineSpacing = 20.0f;
    flowLayout.minimumInteritemSpacing = 10.0f;
    flowLayout.itemSize = CGSizeMake(60.0f, 70.0f);
    flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
    flowLayout.sectionInset = UIEdgeInsetsMake(10.0f, 20.0f, 10.0f, 20.0f);
    return flowLayout;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

    /* Instantiate the collection view controller with a valid flow layout */
    ViewController *viewController =
    [[ViewController alloc]
     initWithCollectionViewLayout:[self flowLayout]];

    self.window = [[UIWindow alloc] initWithFrame:
                   [[UIScreen mainScreen] bounds]];

    self.window.backgroundColor = [UIColor whiteColor];

    /* Set the collection view as the root view controller of our window */
    self.window.rootViewController = viewController;
    [self.window makeKeyAndVisible];
    return YES;

}

Our collection view controller’s implementation stays the same as in Recipe 5.2. If you run your app now, all you will see is a black screen because the default implementation of our collection view controller doesn’t even set the background color of the collection view to white. That’s all right for now. At least our app isn’t crashing anymore because of a lack of layout objects.

5.4. Providing Basic Content to a Collection

Let’s take this one step at a time and start with the fastest and easiest way of creating our cells: instantiate objects of type UICollectionViewCell and feed them to our collection view in our data source. The UICollectionViewCell class has a content view property named contentView, where you can add your own views for display. You can also set various other properties of the cell, such as its background color, which is what we are going to do in this example. But before we begin, let’s first set the expectations of what we are going to achieve in this example code and explain the requirements. We are going to program a collection view with a flow layout that displays three sections, each of which contains anywhere between 20 and 40 cells, with the first section’s cells all being red, the second section’s cells all being green, and the third section’s cells all being blue

#import "ViewController.h"
static NSString *kCollectionViewCellIdentifier = @"Cells";
@implementation ViewController

- (NSArray *) allSectionColors{
    static NSArray *allSectionColors = nil;
    if (allSectionColors == nil){ allSectionColors = @[
                                                       [UIColor redColor],
                                                       [UIColor greenColor],
                                                       [UIColor blueColor],
                                                       [UIColor brownColor],
                                                       ];
    }
    return allSectionColors;
}

After that, override the initWithCollectionViewLayout: designated initializer of your collection view controller and register the UICollectionViewCell with a specific iden‐ tifier. Don’t worry if this makes no sense yet, but look at it this way: for every cell that your collection view has to render, it will first look into a queue of reusable cells and find out if a reusable cell exists. If so, the collection view will pull the cell from the queue, and if not, it will create a new cell and return that to you for configuration. In older versions of iOS, you had to manually create cells if the table view (collection views didn’t exist in older versions of iOS) could not find a reusable cell. However, with the introduction of newer APIs, what Apple has done with regard to reusable cells is very interesting indeed. It has exposed new APIs for both collection and table views so that you can register a call with the table or the collection view, and when you have to configure a new cell, you simply ask the table or the collection view to give you a new cell of that kind. If the cell exists in the reusable queue, it will be given to you. If not, the table or the collection view will automatically create that cell for you. This is called registering a reusable cell, and you can do it in two ways:

  • Register a cell using a class name.

  • Register a cell using a .xib file.

Both these ways of registering reusable cells are good and work perfectly with collection views. To register a new cell with a collection view using the cell’s class name, use the registerClass:forCellWithReuseIdentifier: method of the UICollectionView class, where the identifier is a simple string that you provide to the collection view. When you then attempt to retrieve reusable cells, you ask the collection view for the cell with a given identifier. To register a .xib file with the collection view you need to use the registerNib:forCellWithReuseIdentifier: instance method of your collection view. The identifier of this method also works, as explained earlier in this paragraph. The nib is an object of type UINib, which we will get to use later in this chapter.

- (instancetype) initWithCollectionViewLayout:(UICollectionViewLayout *)layout{
    self = [super initWithCollectionViewLayout:layout];
    if (self != nil){
        /* Register the cell with the collection view for easy retrieval */
        [self.collectionView registerClass:[UICollectionViewCell class]
                forCellWithReuseIdentifier:kCollectionViewCellIdentifier];
    }
    return self;
}

You can see that we are using the kCollectionViewCellIdentifier constant value as the identifier for our cells. We need to define this in our view controller:

import "ViewController.h"
static NSString *kCollectionViewCellIdentifier = @"Cells";
@implementation ViewController

The default implementation of your collection view will have one section unless you implement the numberOfSectionsInCollectionView: method in your data source. We want three sections for our collection view, so let’s implement this method:

- (NSInteger)numberOfSectionsInCollectionView :(UICollectionView *)collectionView{
    return [self allSectionColors].count;
}

Part of the requirement for our application was for each cell to contain at least 20 and at most 40 cells. We can achieve this using the arc4random_uniform(x) function. It returns positive integers between 0 and x, where x is the parameter that you provide to this function. Therefore, to generate a number between 20 and 40, all we have to do is add 20 to the return value of this function while setting x to 20 as well. With this knowl‐ edge, let’s implement the collectionView:numberOfItemsInSection: method of our collection view’s data source:

- (NSInteger)collectionView:(UICollectionView *)collectionView
     numberOfItemsInSection:(NSInteger)section{
    /* Generate between 20 to 40 cells for each section */
    return 5 + arc4random_uniform(5);
}

Last but not least, we want to provide the cells to the collection view. For that we need to implement the collectionView:cellForItemAtIndexPath: method of our collec‐ tion view’s data source:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath{

    UICollectionViewCell *cell =
    [collectionView
     dequeueReusableCellWithReuseIdentifier:kCollectionViewCellIdentifier
     forIndexPath:indexPath];

    cell.backgroundColor = [self allSectionColors][indexPath.section];

    return cell;

}
Clone this wiki locally