Episode #384

Reusable Views

Series: Making a Podcast App From Scratch

10 minutes
Published on March 28, 2019

This video is only available to subscribers. Get access to this video and 572 others.

In this episode we start building our first table view cells. We then build a protocol to represent Reusable Views, such as UITableViewCells. With this protocol you can supply a simple type reference and the reuse identifier and casting happens for you. Leveraging Swift's protocol extensions allows you to leverage your conventions to write cleaner, safer code.

Creating our Cell Subclass

To represent search results, we'll create a custom table view cell:

class SearchResultCell : UITableViewCell {
    @IBOutlet weak var artworkImageView: UIImageView!
    @IBOutlet weak var podcastTitleLabel: UILabel!
    @IBOutlet weak var podcastAuthorLabel: UILabel!
}

Note that each outlet is weak because it will be owned by it's superview. Also note that each of the outlets are implicitly unwrapped optionals. This is because the cell instance is created first, then the outlets are connected from the Storyboard (or XIB).

Reusing Cells

UITableViewDataSource provides a mechanism for reusing cells. It does this by first registering the cell types that are in the storyboard (or cells registered manually in code). Each of these has a reuseIdentifier, which is a string that uniquely describes the type of cell you want to register (or reuse).

Most of the time you'll have a single type of cell, and the common convention is to use the class name to identify it.

We can codify this convention with Swift Protocols.

Creating a Reusable View Protocol

Let's start by defining a protocol called ReusableView:

protocol ReusableView {
    static var reuseIdentifier: String { get }
}

We'll use this to attach some behavior through the use of a protocol extension:

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

Any type that conforms to this protocol will inherit this behavior (though importantly they can override it if the type needs to deviate from the convention).

We're using String(describing: self) here which is a way to get the name of this type (the method is static after all, so self refers to the type itself, not an instance of the type).

We can then adopt this protocol on all table view cells:

extension UITableViewCell : ReusableView { }

Since the default behavior is suitable, we don't have to write any code, we're simply adding the protocol.

Leveraging the ReusableView Protocol

Now that our cells have this protocol, we can extend UITableView to take advantage of it.

extension UITableView {
    func dequeue<T : UITableViewCell>(for indexPath: IndexPath) -> T {
        let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath)
        return cell as! T
    }
}

This method is similar to the built-in dequeue method, but this one is strongly typed (using the generic T) and gets its reuseIdentifier from the protocol.

Back in our view controller, our cellForRowAtIndexPath method gets a lot cleaner!

class SearchViewController: UITableViewController {

    // ...

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // Instead of this
        // let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)

            // We can write this
        let cell: SearchResultCell = tableView.dequeueReusableCell(for: indexPath)

        return cell
    }

    // ...
}

Note that we must specify the type of cell we're attempting to return. This is how the compiler is able to figure out what T is in the generic method call.

This episode uses Swift 4.2, Xcode 10.1.