Implementing a Feed Collection View

Tim Johnson
Swifty Tim
Published in
7 min readSep 4, 2017

I was in Seattle for Labor Day weekend, and I decided to go wild and not labor over the weekend. And no, I didn’t attend Bumbershoot either, regardless of how many times I came up with permutations for its remarkable name.

I’ve found that in a lot of apps I work on, one of the most common elements is a feed. Bizarre right?

It just so happens that along with a feed, there are a lot of different data types and ways to display those data types. If you aren’t too careful, it can get pretty hairy. I’ve run into said hairy situations a countless number of times, and left future developers with unmaintainable codebases because of it. That’s never fun, especially once you start getting those 2am emails from a grumbly friend who’s been pulling their teeth out for the last 8 hours.

Handling that kind of feed in a simple, clean way is actually pretty easy, and I’m going to walk through that today. Specifically, I’m going to walk through

  • Generically displaying data types
  • Adding items to a feed

Generically Displaying Data Types

It’s very easy to get carried away when dealing with a feed that can return multiple data types, which all correspond to a specific display. Switch statements and logic trees seem like a pretty simple starting point, but the more types that get added, the more complicated those statements can become.

Luckily, protocols, as usual, can save the day! Hooray!

For simplicity’s sake, we’re going to assume that we’re using a UICollectionView. It’s a little easier to use when updating compared to a UITableView. We’ll start with a simple protocol for displaying items in the feed.

protocol CollectionItem {}

When I think of the things that I specifically need in order to display items, there’s really only two.

  • A cell
  • A size

The rest is a bunch of extra stuff that isn’t necessary unless you’re getting super fancy, and in that case, you’re going to have more complicated code anyway. We’re going to stick to this scope for now.

As I mentioned before, different data types typically correspond to different displays, so it’s important that each CollectionItem has some way of specifying how to display itself. In this fashion, we need to make our CollectionItem generic, in the sense that it could display any kind of cell.

The way to do this with protocols is to define an associatedType. An associated type is just a fancy way of making a protocol generic. If want to know more, you can read about it here.

protocol CollectionItem {
associatedtype CellType: UICollectionViewCell
}

Great! Now each collection item will have a cell type associated (unintended) with it, defining how it will display itself.

The collection item should also have some way of configuring the cell, otherwise we’d just have some skeleton UI, and nobody wants that.

protocol CollectionItem {
associatedtype CellType: UICollectionViewCell
func configureCell(cell: CellType)
}

Now when the UICollectionView goes to dequeue a cell, it can let the collection item configure it, and we can end up with some splendid UI.

I mentioned size was the second most important aspect to getting data displaying properly in a feed, so let’s go ahead and do something about that.

protocol CollectionItem {
associatedtype CellType: UICollectionViewCell
func configureCell(cell: CellType)
func size(boundedBy width: CGFloat) -> CGSize
}

Note that the size function takes in a width. I typically have something along these lines when calculating the size for a UICollectionViewCell because the height can often depend on the width or vice versa. Self sizing cells are useful, but I have found that at times they are more trouble than necessary.

Before moving on, I’m going to set up some helper methods for UICollectionView to make it easier to work with CollectionItems later on.

extension UICollectionViewCell {
static var identifier: String {
get {
return String(describing: self)
}
}
}
extension UICollectionView {
func registerCollectionItem<T: CollectionItem>(type: T.Type) {
register(T.CellType.self, forCellWithReuseIdentifier: T.CellType.identifier)
}
}

These are providing a cleaner interface for getting a UICollectionViewCell reuse identifier and for registering the corresponding CellType of a CollectionItem.

Voila! We’ve now got our CollectionItem protocol set up and we can start putting labels on screens!

Unfortunately it’s not so easy.

If we wanted to dequeue a cell for a specific CollectionItem at this point, it might look something like this.

There’s a lot of gross looking code there, and it doesn’t even compile.

What this code is trying to say is “Dequeue a cell of CellType, force cast it to CellType, and configure it”. What happens is the compiler has trouble figuring out what is going on with that funky type(of: ), and then casting toCellType, and how to handle it.

An easy solution would be to make the configureCell function on the CollectionItem accept just a UICollectionViewCell, but then we would lose our explicit typing. We don’t want that.

A better solution would be to move the dequeuing logic into the protocol itself. We can do that with an extension.

Here, we don’t have to extract the type from the collection item in order to get the CellType, and the compiler is much happier with the result. Problem solved!

So far so good. If we wanted our collection view datasource to be a list of CollectionItems then we’d probably want something like this in our controller.

var collectionItems: [CollectionItem]

That won’t work either, and Swift will complain that you can only use CollectionItem as a generic constraint because of its associatedType requirement.

We have to do a little bit of magic to make this work, and that comes in the form of wrapping our protocol objects into a typed class. This makes the compiler happy, and as a developer, makes us happy as well.

class AnyCollectionItem {
let size: (CGFloat) -> CGSize
let cell: (UICollectionView, IndexPath) -> UICollectionViewCell
init<U: CollectionItem>(_ collectionItem: U) {
size = collectionItem.size
cell = collectionItem.cell
}
}

We are simply assigning two variables with closure types to the different CollectionItem functions, size and cell. The cell function was added in the extension to make it simpler to dequeue cells from the collection view.

And that’s all we need in order to reference our collection items as an array.

var collectionItems: [AnyCollectionItem]

From here, it’s pretty simple to set up a UICollectionView to display a collection item. A few delegate and datasource methods.

Of course, you’ll want to make sure to register any classes that conform to your CollectionItemprotocol are registred with your collection view. Here is an example class.

class DataItem: CollectionItem {    func size(boundedBy width: CGFloat) -> CGSize {
return CGSize(width: width, height: 80)
}
func configureCell(cell: DataCell) {
// Configure cell here
}
}
class DataCell: UICollectionViewCell {}

Note that because I specify DataCell in the configureCell method, the compiler is able to determine that CellType here is DataCell.

And that’s pretty much all you need to display different data types and their corresponding UI. Each time your designer wants to add a new cell type, just build the UI, assign the corresponding CollectionItem, and profit!

Adding Items to a Feed

Of course, once you’re able to display data of all kinds in your collection view, you’re going to want to make sure you are adding the cells to the collection view in a friendly manner.

When I say friendly manner, I specifically mean in a user friendly way. No blocking the main thread, no weird insertion and deletion crashes, etc.

This is quite simple to do. You just need to follow these steps when updating your collection view.

  1. Dispatch work to a serial background queue
  2. Update your data source
  3. Dispatch work to the main queue
  4. Perform updates on the UICollectionView

Easy.

You want to make sure you’re updating your data source serially on a dedicated thread, so that any operations coming in will be performed in an orderly manner. And this can be done asynchronously or synchronously.

Once you’ve updated your datasource, you want to tell the collection view to update with the desired operations, whether that be insertions or deletions. This can happen either synchronously or asynchronously (although you shouldn’t perform both updating the data source and updating the collection view synchronously). For simplicity sake, all I’m doing here is insertion of CollectionItems.

First lets create our dedicated background queue.

let dispatchQueue = DispatchQueue(label: UUID().uuidString, qos: .background)

Note that this is a queue that runs on the main thread, but it has background priority. This allows work to happen, but in such a way that it doesn’t “block” the main thread.

Then we want to perform our insertion operation.

I’ll go through this step by step.

First, we dispatch any work to our dispatchQueue. This allows any kind of data manipulation to happen on the serial queue we specified. This makes this operation thread safe, which is important if different threads coming from random places in the application are trying to add items.

Next, we build our indexPathsToInsert using the number of items being added and the current number of items existing in our collectionItems array. This is important to hold a reference to, as we’ll need to tell the UICollectionView what index path operations to perform.

After that, we’re simply adding the items to our collectionItems storage.

And finally, we perform the actual work on the collectionView, telling it to insert the items at the specified index paths. Here, we are calling performBatchUpdate.This is the Apple suggested way of performing any updates to a UICollectionView, and we are calling it on the main queue, also recommended by Apple.

I’ve dispatched back to the main queue synchronously, but because performBatchUpdates is an asynchronous operation, it doesn’t really make a difference how you choose to dispatch back to the main queue.

That’s it! Now we can (thread)safely add items to our collection view without blocking the main thread!

At first, dealing with all the different kinds of cell displays and data types in a complicated feed can be overwhelming. But it really all boils down to

  • What is being displayed
  • How is it being displayed

and those core concepts can be nicely wrapped into a protocol.

Making sure to put all of the work to update your data on its own queue can foster a thread safe environment, and a better experience for your end user.

I hope this was a good read, and feel free to provide and feedback!

--

--