Lecture Handout

This lecture covers UICollectionViews -- a widely used UIKit component. You'll notice that UICollectionViews are very similar to UITableViews!

What is a UICollectionView?

A collection view (UICollectionView) is a subclass of UIScrollView, which means that the user can scroll through it, either horizontally or vertically. Just like a table view, a collection view consists of sections where each section consists of 3 parts: a header, cells, and a footer. However, unlike a table view (in which each cell is constrained to be the full width of the table view), the size of the cells in a collection view can be set. In other words, a collection view is a more dynamic, customizable table view.

Why are UICollectionViews Important?

Like table views, UICollectionViews are an extremely powerful type of view and are used in almost all of the successful apps today (i.e. Instagram, Photos, Stories, etc.). They provide an easy way to display a lot of information in a compact, grid-like manner.

UICollectionView Examples

Example 1: Netflix Each category (e.g. "Ensemble TV Shows", "Women Who Rule The Screen", etc.) is a section is a UICollectionView. Each show is a UICollectionViewCell.

Example 2: Instagram The entire feed is a UICollectionView, and each image is a UICollectionViewCell.

Using a UICollectionView

Setting Up a UICollectionView

Similar to a table view, in addition to the usual initialization process for initializing any UIView (calling the init function, set translatesAutoresizingMaskIntoConstraints to false, adding the view as a subview to parent view, and setting up constraints), there are 4 additional things that you should do:

  1. Set the collectionView’s delegate

  2. Set the collectionView’s dataSource

  3. Register your custom UICollectionViewCell class

  4. (Only for UICollectionViews!) Initialize the collectionView with a

    UICollectionViewLayout (we use UICollectionViewFlowLayout). Your collectionView’s delegate should also conform to UICollectionViewDelegateFlowLayout.

For explanations on what the first 3 mean, refer to Lecture 4: UITableViews.

In terms of 4, the initializer for a UICollectionView takes in a frame: CGRect and a layout: UICollectionViewLayout. In most cases, we just use an instance of UICollectionViewFlowLayout for this layout parameter. Why? Well, a UICollectionViewFlowLayout is a “layout object that organizes items into a grid with optional header and footer views for each section” -- which is what we want in most cases!

Some nice properties of UICollectionViewFlowLayout that we can set to customize our collectionView are scrollDirection (vertical or horizontal), minimumLineSpacing (the minimum spacing to use between rows in the grid), minimumInteritemSpacing (the minimum spacing to use between items in the same row), and a lot more.

The following is an example of initializing a UICollectionView:

let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = padding // optional
layout.minimumInteritemSpacing = padding // optional
collectionView = UICollectionView(frame: .zero, collectionViewLayout:layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(PhotoCollectionViewCell.self,forCellWithReuseIdentifier: photoCellReuseIdentifier)

Let's go through what this code does, line by line. 1: Initialize a UICollectionFlowLayout instance. 2: Set the layout's scrollDirection to be vertically-scrolling. 3 & 4: Set the minimumLineSpacing and minimumInteritemSpacing to be some constant we defined in a padding variable. This means that items in the same row will have spacing between them equal to the value stored in padding and items on different rows will also have spacing between them equal to padding. (Like the Instagram example!) 5: Initialize our collectionView using the layout instance we just created. 6: Set translatesAutoresizingMaskIntoConstraints to false. 7 & 8: Set the delegate and dataSource to be self, which is the view controller that holds this collectionView. 9: Register our custom PhotoCollectionViewCell to the collectionView. This tells collectionView that we will be dequeueing PhotoCollectionViewCells.

Important Protocols: UICollectionViewDataSource, UICollectionViewDelegate, and UICollectionViewDelegateFlowLayout

We look at this diagram again to understand the delegation that occurs. Even though the diagram uses UITableViewDataSource /UITableViewDelegate, this is basically the same as UICollectionViewDataSource/UICollectionViewDelegate except replace “rows” with “items”.

1. UICollectionViewDataSource

A UICollectionViewDataSource (in most cases, the view controller) must conform to certain methods and is “responsible for providing the data and views required by a collection view.” The one method that you should always implement is:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
	let cell = collectionView.dequeueReusableCell(withReuseIdentifier: photoCellReuseIdentifier, for: indexPath) as! PhotoCollectionViewCell
	let dataModel = dataModelArray[indexPath.item]
	cell.configure(for: dataModel)
	return cell
}

We'll often refer to this method as cellForItemAt. This function requires you to return a UICollectionViewCell which will be inserted at a particular location in the collectionView.

One parameter in this function that is important to take note of is indexPath, which is of type IndexPath. The indexPath has two important properties that you may sometimes need: 1. item (Note: for tableViews, we have rows instead of items), and 2. section Thus, if you wanted to see what item or section the cell you’re returning is going to be used for, you can just call indexPath.item or indexPath.section.

Once again, let's go through the code line by line: 2. Dequeue the appropriate cell using the associated reuse identifier that you used when you registered your custom cell to the collectionView. Make sure you cast the dequeued cell as your custom cell class! 3 & 4. It's best practice to have some configure(...) function as a part of your custom cell class that takes in some data or model object to configure the views inside your cell. Let's say you're using some dataModelArray as the data source for your collection view. You can access the dataModel object that this item represents using dataModelArray[indexPath.item]. 5. Return the configured cell!

Another method that you should implement is:

func collectionView(_ collectionView: UICollectionView, 
numberOfItemsInSection section: Int) -> Int {
   return dataModelArray.count
 }

We'll refer to this method as numberOfItemsInSections.This function requires you to return an integer representing the number of rows that the section section should have. Usually, the number of cells that you want to display in your collection view is dependent on the number of model objects that you have. For example, if you wanted to use a collection view to display a collection of photos, you would probably have an array of Photo objects. Thus, the number of cells you need in your table view is just the number of photos in that array, which you can get from photosArray.count. However, it is also possible that you just want to create a static number of cells, in which case you can just return that constant!

Finally, while not required, you may also want to implement the following:

 func numberOfSections(in collectionView: UICollectionView) -> Int {
   return 2
 }

As the name implies, this allows you to control the number of sections that your collection view has. If you don't implement this function, your collection view will by default have one section.

2. UICollectionViewDelegate

A UICollectionViewDelegate does not actually have to conform to any specific methods since all the methods are optional. However, one useful one that you may find yourself using a lot is:

func collectionView(_ collectionView: UICollectionView,
 didSelectItemAt indexPath: IndexPath) {
  // Perform some action upon selecting an item
}

We'll refer to this function as didSelectItemAt. This is the function that gets called whenever one of the cells in collectionView is selected by the user. Thus, if you want to trigger some sort of action or animation upon selection, this is the place you would do it. For example, if you wanted to change some data model property upon selecting a cell, you could once again access that specific data model object using indexPath.item.

3. UICollectionViewFlowLayoutDelegate

Lastly, a UICollectionViewDelegateFlowLayout also does not actually have to conform to any specific methods since all the methods are optional. However, a method that you should implement is:

 func collectionView(_ collectionView: UICollectionView, layout
 collectionViewLayout: UICollectionViewLayout, sizeForItemAt
 indexPath: IndexPath) -> CGSize {
   return CGSize(width: 50, height: 50)
 }

We'll refer to this method as sizeForItemAt. This method requires you to return a CGSize that represents the size of the cell at indexPath. Thus, if we wanted each of our cells to be 50 pixels by 50 pixels, we would return CGSize(width: 50, height: 50), as in the example code snippet.

Reference

You can find a list of all the UICollectionViewDelegate, UICollectionViewDataSource, & UICollectionViewDelegateFlowLayout methods in the Apple documentation, linked below.

PDF Download

Last updated