2️⃣UICollectionView Setup

Fall 2023 | Vin Bui

Create a Custom UICollectionViewCell

Creating a UICollectionViewCell is very similar to creating a UITableViewCell. However, there are just minor syntax changes. Follow these steps:

Only Steps 1 and 2 are different when creating a UICollectionViewCell compared to a UITableViewCell

  1. Our custom class needs to be a subclass of UICollectionViewCell

    • class CustomCollectionViewCell: UICollectionViewCell { }

  2. Create the following initializer:

    // Compare this with a `UITableViewCell`
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    // `required init` here
  3. Determine what views to create and write a helper function to initialize its properties.

    • For example, if we need to display some text, we would create a UILabel and create a helper function to initialize its font, font color, etc. Note that we do not know anything about the data yet, so the property .text of the UILabel will not be initialized yet.

  4. Inside of the helper function, add the views we created as a subview to contentView and constrain the view with respect to contentView. Then call the helper function inside of the initializer.

    • This is one of the main differences from what we have been doing before. Instead of referencing view, we will be using contentView. Note: we do not need to use safeAreaLayoutGuide here.

  5. Create a configure function (do not make private) that will take in some data as a parameter, and configure our views.

    • For example, we could write a function that takes in a String and sets UILabel.text property equal to the value passed in.

  6. Create a reuse identifier for this cell: static let reuse = "<reuse_identifier>"

    • See “Dequeuing Cells” below for more information.

// 1. Subclass of `UICollectionViewCell`
class CustomCollectionViewCell: UICollectionViewCell {
    // 3. Create view properties
    private let label = UILabel()

    // 6. Create a reuse identifier
    static let reuse = "CustomCollectionViewCellReuse"

    // 2. Create the following init
    override init(frame: CGRect) {
        super.init(frame: frame)

        // 4. Call helper functions
	setupLabel()
    }
		
    // 2. `required init` here

    // 5. `configure` function (do not make private)
    func configure(newText: String) {
	label.text = newText
	// Configure additional views here
    }

    // 3. Set Up View Helpers
    private func setupLabel() {
	// 3. Initialize the label's properties

	// 4. Add as subview to `contentView`

	// 4. Constrain with respect to `contentView`
    }
}

Dequeuing Cells

The idea behind this concept is the exact same for that of a UITableViewCell. Read it here:

Setting Up a UICollectionView

A UICollectionView is just like any other UIView that we've worked with thus far. We've initialized the view by doing the following steps:

  1. Create the view

  2. Configure the view by changing its properties

  3. Adding the view as a subview to some parent view

  4. Enable auto layout and set up constraint

With a UICollectionView, we do the exact same thing but with additional steps:

  1. Register a UICollectionViewCell

    • For example, if we had a custom class called CustomCollectionViewCell with a static reuse constant called reuse, we would use the following code:

collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CustomCollectionViewCell.reuse)
  1. Set the UICollectionView delegate (create an extension just like any other protocol)

  2. Set the UICollectionView dataSource (create an extension just like any other protocol)

Every step that we mentioned above is very similar to that of a UITableViewCell; however, there is 1 more additional step.

  1. Initialize the collection view with a UICollectionViewFlowLayout and conform to UICollectionViewDelegateFlowLayout

6: UICollectionViewDelegate

The purpose of a UICollectionViewDelegate is to add functionality to the collection view. A class conforming to the protocol UICollectionViewDelegate does not have any required functions to implement; however, the most common function to implement is: didSelectItemAt.

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    // Perform some operation when cell is tapped
}

7: UICollectionViewDataSource

In contrast to UICollectionViewDelegate, there are two required functions to implement: cellForItemAt and numberOfItemsInSection. The idea is exactly the same as that of a table view, but with minor syntax changes (”item” instead of “row”).

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

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withIdentifier: CustomCollectionViewCell.reuse, for: indexPath) as? CustomCollectionViewCell else { return UICollectionViewCell() }
    
    let dataModel = dataModelArray[indexPath.row]
    cell.configure(...) // pass in our dataModel to the configure function in our custom cell class
    return cell
}

Read the details here:

8: UICollectionViewFlowLayout

Everything we’ve mentioned earlier is very similar to a table view but with minor syntax changes. However, there is one more additional step that is required by a collection view that gives it customization benefits.

Create a FlowLayout and Initialize CollectionView

Inside of the helper function that sets up the collection view, add the following lines of code:

let layout = UICollectionViewFlowLayout()
layout.scrollDirection = // .vertical or .horizontal
layout.minimumLineSpacing = // (optional) spacing amount
layout.minimumInteritemSpacing = // (optional) spacing amount

// Initialize CollectionView with the layout
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)

Let’s go over this line by line:

  1. Create a UICollectionViewFlowLayout instance

  2. (REQUIRED) Set the collection view’s scroll direction: .vertical or .horizontal

  3. (OPTIONAL) Set the spacing between each line (top and bottom)

  4. (OPTIONAL) Set the spacing between each item (left and right)

  5. Initialize CollectionView with the layout we just created

Conform to UICollectionViewDelegateFlowLayout

In the previous step, we configured the collection view’s layout. Remember how the UICollectionViewDelegate did not have a heightForRowAt function like a table view does? Well that’s because each item (cell) has a customizable height and width whereas in a table view, we could only customize the row’s height. To do this, just create an extension and conform to UICollectionViewDelegateFlowLayout and add this function:

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

If we want to configure the size of each item relative to the size of the collection view, we can use collectionView.frame. For example, if we want the item cell to be half of the width of the collection view, we can do collectionView.frame.width / 2.

Complete Code

class ViewController: UIViewController {
    // 1. Create the property BUT DONT INITIALIZE IT YET
    // Note that this is different from a table view
    private var collectionView: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Additional setup here

	setupCollectionView() // 2. Configure the view
    }
		
    // 2. Configure the view
    private func setupCollectionView() {
	// 8. Create a FlowLayout
	let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = // .vertical or .horizontal
        layout.minimumLineSpacing = // (optional) spacing amount
        layout.minimumInteritemSpacing = // (optional) spacing amount

	// Initialize CollectionView with the layout
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)				
	collectionView.register(CustomTableViewCell.self, forCellWithReuseIdentifier: CustomTableViewCell.reuse) // 5
	collectionView.delegate = self // 6
	collectionView.dataSource = self // 7
		
	view.addSubview(collectionView) // 3
	collectionView.translatesAutoresizingMaskIntoConstraints = false // 4
		
	// 4. Set constraints
    }
}

// 6. Conform to `UICollectionViewDelegate`
extension ViewController: UICollectionViewDelegate {
    // `didSelectItemAt` (optional)
    // Additional functions here
}

// 7. Conform to `UICollectionViewDataSource`
extension ViewController: UICollectionViewDataSource {
    // `cellForItemAt`
    // `numberOfItemInSection`
    // Additional functions here
}

// 8. Conform to `UICollectionViewDelegateFlowLayout`
extension ViewController: UICollectionViewDelegateFlowLayout {
    // `sizeForItemAt`
    // Additional functions here
}

Comparing with a UITableView

A lot of the material mentioned above is very repetitive and already seen in a UITableView. For comparison purposes, here are the main differences between the two when setting them up:

  1. Create a subclass of UICollectionViewCell instead

    • The init function is different

  2. A flow layout is required when initializing the collection view

    • You may have noticed that instead of using private let collectionView = UICollectionView() we used private var collectionView: UICollectionView!. The reason for this is because we have to pass in a layout when initializing the collection view. By replacing it with this line, we are making a promise that we will initialize the collection view later. (There is a cleaner way to do this but it is a bit advanced for now).

  3. You must conform to UICollectionViewDelegateFlowLayout

    • Implement the sizeForItemAt function

  4. Syntax: use “items” instead of “rows”

    • Ex: cellForItemAt instead of cellForRowAt. However, the implementation is the exact same.

Tip: If we start typing the function name such as cellForItemAt, we can choose which one we want and Xcode will autofill the function header for us!

Last updated