Buddybuild is a CI, CD and user feedback platform built for mobile development teams - sign up for free and start building better apps!

UICollectionView has always been a powerhouse. Its variety and versatility are tough to understate.

We've covered it a bit here on LBOC, but we've still only just scratched the surface. Today we'll begin to dive deeper in what all it can do by customizing how new collection view cells animate when they're being inserted. Let's begin!

We'll start by creating a new single view app in Xcode.

We'll drag out a UICollectionViewController in Interface Builder, set its class to be the ViewController one that comes with the Xcode template, then update ViewController.swift to have some basics:

class ViewController: UICollectionViewController {
  var items = [Item]()

  func addItem() { items.append(Item(color: .random())) }

  override func viewDidLoad() {
    super.viewDidLoad()
    for _ in 0...10 { addItem() }
  }

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

  override func collectionView(
    _ collectionView: UICollectionView, 
    cellForItemAt indexPath: IndexPath
  ) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(
      withReuseIdentifier: "ItemCell", 
      for: indexPath
    )

    cell.contentView
      .backgroundColor = items[indexPath.item].color

    return cell
  }  
}

Next, we'll embed it in a UINavigationController, and add a UIBarButtonItem to add new items.

We'll Control+Drag the bar button item into our ViewController.swift as a new add: action. We'll wire up it like so:

@IBAction func add(_ sender: UIBarButtonItem) {
  addItem()

  let indexPath = IndexPath(
    item: self.items.count - 1, 
    section: 0
  )

  collectionView?.performBatchUpdates({
    self.collectionView?.insertItems(at: [indexPath])
  }, completion: nil)
}

With this, we have a nice mostly-"boilerplate" setup. Just some square, randomly-colored, cells that fade in when they're inserted:

Not bad. Now let's customize the behavior of those cells as they're being inserted. For this, we'll need our own custom UICollectionViewLayout subclass.

Specifically we'll subclass UICollectionViewFlowLayout and override a few functions to customize the behavior of inserted cells.

class CustomFlowLayout : UICollectionViewFlowLayout {
  var insertingIndexPaths = [IndexPath]()
}

We've added a property to keep track of which index paths are being inserted during each batch update of the collection view.

To populate our property we'll need to override a couple of functions:

override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
  super.prepare(forCollectionViewUpdates: updateItems)

  insertingIndexPaths.removeAll()

  for update in updateItems {
    if let indexPath = update.indexPathAfterUpdate,
                       update.updateAction == .insert {
      insertingIndexPaths.append(indexPath)
    }
  }
}

override func finalizeCollectionViewUpdates() {
  super.finalizeCollectionViewUpdates()

  insertingIndexPaths.removeAll()
}

Nice. Nothing fancy here, we're just collecting the inserted index paths at the beginning of each update, then clearing them out at the end.

The real magic happens when we override one last function:

override func initialLayoutAttributesForAppearingItem(
  at itemIndexPath: IndexPath
) -> UICollectionViewLayoutAttributes? {
  let attributes = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath)

  if insertingIndexPaths.contains(itemIndexPath) {
    attributes?.alpha = 0.0
    attributes?.transform = CGAffineTransform(
      scaleX: 0.1, 
      y: 0.1
    )
  }

  return attributes
}

What we've done here is to grab the layout attributes our collection view was going to use for the newly inserted item, and modify them slightly, before passing them along back to the system.

Here we've added a transform to scale the item down when it is first added, this will give us a nice "zoom up" effect as each item is added:

That looks neat, but let's try for something even funkier. We can change our transform line to:

attributes?.transform = CGAffineTransform(
  translationX: 0, 
  y: 500.0
)

With this one change, we can achieve an entirely different effect:

Neat!

Download the Xcode project we built in this Bite right here.

That's all for today. Have a specific UICollectionView question you'd like answered? Send it along!.