We added quite a bit of code to the project in the past episodes and yet the user interface hasn't changed. It's time to use the data from the Cocoacasts API to populate the feed view controller's view.

Adding a Collection View

We won't be using a table view to display the episodes in the feed view controller. A collection view offers more flexibility. The Cocoacasts client should also look great on iPad and a collection view is the better choice to achieve that goal.

We start this episode with the FeedViewController class. We define an outlet, collectionView, of type UICollectionView!. I almost always configure user interface elements in the view controller using a didSet property observer. I find it easier and more efficient to read a few lines of code than opening a storyboard or XIB file to understand how a user interface element is configured. We assign the view controller as the delegate and the data source of the collection view.

@IBOutlet var collectionView: UICollectionView! {
    didSet {
        // Configure Collection View
        collectionView.delegate = self
        collectionView.dataSource = self
    }
}

The compiler warns us that the FeedViewController class doesn't conform to the UICollectionViewDelegate and UICollectionViewDataSource protocols. We create an extension for the FeedViewController class to conform FeedViewController to the UICollectionViewDataSource protocol. We also add stub methods for the required methods of the UICollectionViewDataSource protocol, collectionView(_:numberOfItemsInSection:) and collectionView(_:cellForItemAt:).

extension FeedViewController: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

    }

}

We create another extension for the FeedViewController class to conform FeedViewController to the UICollectionViewDelegate and UICollectionViewDelegateFlowLayout protocols. We leave the extension empty for now.

extension FeedViewController: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

}

Open Feed.storyboard and select the view of the feed view controller. Open the Attributes Inspector on the right and set Background to White Color. Open the Object Library and drag a collection view to the feed view controller's view. Set Items to 0 in the Attributes Inspector on the right. Pin the collection view to the edges of its superview.

Creating the User Interface of the Feed View Controller

Select the feed view controller, open the Connections Inspector on the right, and connect the collectionView outlet to the collection view.

Defining a Public API

The view model of the feed view controller manages the array of episodes and the feed view controller doesn't have direct access to the array of episodes. This means that the view model should expose an API that allows the feed view controller to populate its collection view. This isn't difficult.

Open FeedViewModel.swift and define a private, variable property, episodes, of type [Episode]. The default value of the episodes property is an empty array.

import Foundation

final class FeedViewModel {

    // MARK: - Properties

    private let apiClient: APIClient

    // MARK: -

    private var episodes: [Episode] = []

    ...
}

To implement the required methods of the UICollectionViewDataSource protocol, the feed view controller needs to know how many episodes it should display. We define a computed property, numberOfEpisodes, of type Int. As the name implies, it returns the number of objects in the array of episodes.

// MARK: - Public API

var numberOfEpisodes: Int {
    return episodes.count
}

The feed view controller also needs an Episode object to configure each cell of the collection view. We implement a method, episode(at:), that returns the Episode object for a particular index. The episode(at:) method gives the feed view controller access to a model object and that is a red flag since the project adopts the MVVM pattern. We improve the implementation later. Don't worry about it for now.

func episode(at index: Int) -> Episode {
    return episodes[index]
}

Implementing the UICollectionViewDataSource Protocol

We now have an API we can use to implement the methods of the UICollectionViewDataSource protocol. Open FeedViewController.swift. Implementing the collectionView(_:numberOfItemsInSection:) method is straightforward. We ask the view model for the number of episodes. Since the viewModel property is of an optional type, we return 0 if viewModel is equal to nil. This should never happen, though.

extension FeedViewController: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return viewModel?.numberOfEpisodes ?? 0
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

    }

}

Creating a Collection View Cell

Before we can implement the collectionView(_:cellForItemAt:) method, we need to create a UICollectionViewCell subclass that can display an episode. Because I plan to reuse the UICollectionViewCell subclass in several places, using a XIB file is the better choice. Since I plan to reuse the UICollectionViewCell subclass in several places, I won't add it to the Feed group. We create a group with name Views and add a subgroup with name Collection View Cells to the Views group.

We add a UICollectionViewCell subclass to the Collection View Cells group, name the subclass EpisodeCollectionViewCell, and check the Also create XIB file checkbox.

Creating a UICollectionViewCell Subclass

We start with a simple user interface. We add an image view at the top and a label at the bottom. The label is centered horizontally in its superview. To make sure the text of the label stays within the bounds of the collection view cell, we define a leading and trailing constraint and set Relation to Greater Than or Equal in the Size Inspector on the right. The user interface isn't that important. We refine it later.

Creating a UICollectionViewCell Subclass

Open EpisodeCollectionViewCell.swift in the Assistant Editor on the right. We define an outlet for the image view, imageView, and an outlet for the label, titleLabel.

import UIKit

class EpisodeCollectionViewCell: UICollectionViewCell {

    // MARK: - Properties

    @IBOutlet weak var imageView: UIImageView!

    // MARK: -

    @IBOutlet weak var titleLabel: UILabel!

    // MARK: - Overrides

    override func awakeFromNib() {
        super.awakeFromNib()
    }

}

Protocols and Extensions

We have everything we need to implement the collectionView(_:cellForItemAt:) method of the UICollectionViewDataSource protocol. To make the implementation easier and more elegant, I would like to take advantage of a pattern I really enjoy using. We add a Swift file to the Protocols group and name it ReusableView.swift. We add an import statement for the UIKit framework and define a protocol with name ReusableView. The protocol defines a static, variable property, reuseIdentifier, of type String.

import UIKit

protocol ReusableView {

    static var reuseIdentifier: String { get }

}

Any type conforming to the ReusableView protocol can return its reuse identifier. We take it one step further by providing a default implementation using an extension. Any type conforming to the ReusableView protocol has a static property with name reuseIdentifier.

extension ReusableView {

    static var reuseIdentifier: String {
        return String(describing: self)
    }

}

The only thing left to do is conform the UICollectionViewCell class to the ReusableView protocol. Because we provided a default implementation for reuseIdentifier, we can leave the implementation of the extension empty.

extension UICollectionViewCell: ReusableView {}

Dequeuing collection view cells is a tedious task. Let's use generics to make it virtually painless. Create a group, Extensions, and add a Swift file with name UICollectionView+Helpers.swift to the Extensions group. Replace the import statement for Foundation with an import statement for UIKit.

We define another extension. This time we create an extension for the UICollectionView class. We define one method, dequeueReusableCell(for:), that accepts one argument of type IndexPath. Notice that it doesn't accept a reuse identifier as an argument.

import UIKit

extension UICollectionView {

    func dequeueReusableCell<T: UICollectionViewCell>(for indexPath: IndexPath) -> T {

    }

}

The goal is to ask a collection view for a reusable collection view cell by providing nothing more than an index path. This is possible if we tell the compiler what type of collection view cell we expect. Notice that the return type of the dequeueReusableCell(for:) method is of type T, a generic type.

We also define a type constraint in angle brackets. This may seem complex, but it really isn't. The object returned from the dequeueReusableCell(for:) method is required to inherit from UICollectionViewCell.

In the method's body, we dequeue a collection view cell by invoking dequeueReusableCell(withIdentifier:for:). Because T needs to inherit from UICollectionViewCell and UICollectionViewCell conforms to the ReusableView protocol, we can ask it for its reuse identifier by accessing the value of its reuseIdentifier property. We cast the result to T. If the cast fails, we throw a fatal error.

import UIKit

extension UICollectionView {

    func dequeueReusableCell<T: UICollectionViewCell>(for indexPath: IndexPath) -> T {
        guard let cell = dequeueReusableCell(withReuseIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
            fatalError("Unable to Dequeue Reusable Collection View Cell")
        }

        return cell
    }

}

Populating the Collection View

With the ReusableView protocol and extensions in place, the implementation of the collectionView(_:cellForItemAt:) method becomes trivial. We use a guard statement to ask the view model for the Episode object that corresponds with the index path. We throw a fatal error if no Episode object is available because that should never happen.

The next step is asking the collection view for an episode collection view cell. This is trivial thanks to the extension we implemented a few moments ago. We configure the cell using the Episode object and return it.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    // Fetch Episode
    guard let episode = viewModel?.episode(at: indexPath.item) else {
        fatalError("No Episode Available")
    }

    // Dequeue Episode Collection View Cell
    let cell: EpisodeCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath)

    // Configure Cell
    cell.titleLabel.text = episode.title

    return cell
}

To define the size and position of the collection view cells, we implement two methods of the UICollectionViewDelegateFlowLayout protocol. The first method we implement is collectionView(_:layout:sizeForItemAt:). We keep the implementation simple for the time being. Each collection view cell should be as wide as the collection view and 200.0 points high.

extension FeedViewController: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

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

}

The second method we implement is collectionView(_:layout:minimumLineSpacingForSectionAt:). As the name implies, this method returns the minimum spacing between successive rows of a section.

extension FeedViewController: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

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

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 20.0
    }

}

The next step is registering the XIB file of the EpisodeCollectionViewCell class with the collection view. We handle this step in the didSet property observer of the collectionView property. You probably know by now that I try to avoid string literals as much as possible. Let me show you another trick I use in every project I work on.

We add a Swift file to the Extensions group, name it UICollectionViewCell+Helpers.swift, and add an import statement for the UIKit framework. We create an extension for the UICollectionViewCell class and define a static, variable property, nibName, of type String. The value of this computed property is the name of the class. Every UICollectionViewCell subclass exposes this static computed property.

import UIKit

extension UICollectionViewCell {

    static var nibName: String {
        return String(describing: self)
    }

}

With the extension in place, we revisit FeedViewController.swift. In the didSet property observer of the collectionView property, we create an instance of the UINib class, passing in the name of the XIB file of the EpisodeCollectionViewCell class and a reference to the main bundle. To register the XIB file of the EpisodeCollectionViewCell class with the collection view, we invoke register(_:forCellWithReuseIdentifier:) on the collection view, passing in the UINib instance and the reuse identifier of the EpisodeCollectionViewCell class.

@IBOutlet var collectionView: UICollectionView! {
    didSet {
        // Configure Collection View
        collectionView.delegate = self
        collectionView.dataSource = self

        // Register Episode Collection View Cell
        let xib = UINib(nibName: EpisodeCollectionViewCell.nibName, bundle: .main)
        collectionView.register(xib, forCellWithReuseIdentifier: EpisodeCollectionViewCell.reuseIdentifier)
    }
}

Thanks to the foundation we laid earlier, we don't need to mess with string literals and typos are not an issue.

Updating the Collection View

If we run the application in the simulator, the collection view is still blank. This isn't surprising since the API request completes a few moments after the collection view has updated its interface. The moment the feed view controller populates the collection view, the API request is still in flight. The problem is that the collection view isn't updated when the API request completes. This is easy to fix.

The feed view model should notify the feed view controller every time the array of episodes changes. We have several options to notify the feed view controller. A closure is one of the more elegant solutions. Let me show you the solution I have in mind.

Open FeedViewModel.swift and define a variable property with name episodesDidChange. The type of the property is a closure that accepts no arguments and returns Void.

import Foundation

final class FeedViewModel {

    // MARK: - Properties

    private let apiClient: APIClient

    // MARK: -

    private var episodes: [Episode] = []

    // MARK: -

    var episodesDidChange: (() -> Void)?

    ...
}

The feed view controller invokes the episodesDidChange handler every time the episodes property changes. A didSet property observer makes this easy, but there's a subtle detail we need to be mindful of. The feed view controller should not need to worry about threading issues. In other words, it should not need to know or worry on which thread the episodesDidChange handler is invoked. To avoid any confusion, the feed view model invokes the episodesDidChange handler on the main thread using Grand Central Dispatch.

import Foundation

final class FeedViewModel {

    // MARK: - Properties

    private let apiClient: APIClient

    // MARK: -

    private var episodes: [Episode] = [] {
        didSet {
            DispatchQueue.main.async {
                self.episodesDidChange?()
            }
        }
    }

    // MARK: -

    var episodesDidChange: (() -> Void)?

    ...
}

Before we install the handler in the feed view controller, we update the initializer. In the completion handler of the fetchEpisodes(_:) method, we update the episodes property. That in turn invokes the episodesDidChange handler, notifying the feed view controller.

// MARK: - Initialization

init(apiClient: APIClient) {
    // Set Properties
    self.apiClient = apiClient

    // Fetch Episodes
    apiClient.fetchEpisodes { [weak self] (result) in
        switch result {
        case .success(let episodes):
            print(episodes.count)
            self?.episodes = episodes
        case .failure(let error):
            print(error)
        }
    }
}

Tying Everything Together

To tie everything together, we revisit the FeedViewController class. We create a helper method, setupViewModel(), in which the feed view controller installs the episodesDidChange handler. We keep it simple and call reloadData() on the collection view in the closure. Notice that we weakly reference self, the feed view controller, in the closure.

// MARK: - Helper Methods

private func setupViewModel() {
    // Install Handler
    viewModel?.episodesDidChange = { [weak self] in
        // Update Collection View
        self?.collectionView.reloadData()
    }
}

We invoke setupViewModel() in the viewDidLoad() method of the FeedViewController class.

// MARK: - View Life Cycle

override func viewDidLoad() {
    super.viewDidLoad()

    // Setup View Model
    setupViewModel()
}

Build and Run

It's time to build and run the application in the simulator. We finally have data to show to the user. It doesn't look pretty, but it's a start. In the next episodes, we refine the implementation and build a robust user experience.