Getting Started

This guide provides a brief overview for how to get started using IGListKit.

Creating your first list

After installing IGListKit, creating a new list is easy.

Creating a section controller

Creating a new section controller is simple. Subclass IGListSectionController and override at least cellForItemAtIndex: and sizeForItemAtIndex:.

Take a look at LabelSectionController for an example section controller that handles a String and configures a single cell with a UILabel.

class LabelSectionController: ListSectionController {
  override func sizeForItem(at index: Int) -> CGSize {
    return CGSize(width: collectionContext!.containerSize.width, height: 55)
  }

  override func cellForItem(at index: Int) -> UICollectionViewCell {
    return collectionContext!.dequeueReusableCell(of: MyCell.self, for: self, at: index)
  }
}

Creating the UI

After creating at least one section controller, you must create a UICollectionView and IGListAdapter.

let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)

let updater = ListAdapterUpdater()
let adapter = ListAdapter(updater: updater, viewController: self)
adapter.collectionView = collectionView

Note: This example is done within a UIViewController and uses both a stock UICollectionViewFlowLayout and IGListAdapterUpdater. You can use your own layout and updater if you need advanced features!

Connecting the data source

The last step is the IGListAdapter‘s data source and returning some data.

func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
  // this can be anything!
  return [ "Foo", "Bar", 42, "Biz" ]
}

func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
  if object is String {
    return LabelSectionController()
  } else {
    return NumberSectionController()
  }
}

func emptyView(for listAdapter: ListAdapter) -> UIView? {
  return nil
}

After you have created the data source you need to connect it to the IGListAdapter by setting its dataSource property:

adapter.dataSource = self

You can return an array of any type of data, as long as it conforms to IGListDiffable.

Immutability

The data should be immutable. If you return mutable objects that you will be editing later, IGListKit will not be able to diff the models accurately. This is because the instances have already been changed. Thus, the updates to the objects would be lost. Instead, always return a newly instantiated, immutable object and implement IGListDiffable.

Diffing

IGListKit uses an algorithm adapted from a paper titled A technique for isolating differences between files by Paul Heckel. This algorithm uses a technique known as the longest common subsequence to find a minimal diff between collections in linear time O(n). It finds all inserts, deletes, updates, and moves between arrays of data.

To add custom, diffable models, you need to conform to the IGListDiffable protocol and implement diffIdentifier() and isEqual(toDiffableObject:).

Note: an object’s diffIdentifier() should never change. If an object mutates it’s diffIdentifer() the behavior of IGListKit is undefined (and almost assuredly undesirable).

For an example, consider the following model:

class User {
  let primaryKey: Int
  let name: String
  // implementation, etc
}

The user’s primaryKey uniquely identifies user data, and the name is just the value for that user.

Let’s say a server returns a User object that looks like this:

let shayne = User(primaryKey: 2, name: "Shayne")

But sometime after the client receives shayne, someone changes their name:

let ann = User(primaryKey: 2, name: "Ann")

Both shayne and ann represent the same unique data because they share the same primaryKey, but they are not equal because their names are different.

To represent this in IGListKit’s diffing, add and implement the IGListDiffable protocol:

extension User: ListDiffable {
  func diffIdentifier() -> NSObjectProtocol {
    return primaryKey
  }

  func isEqual(toDiffableObject object: Any?) -> Bool {
    if let object = object as? User {
      return name == object.name
    }
    return false
  }
}

The algorithm will skip updating two User objects that have the same primaryKey and name, even if they are different instances! You now avoid unnecessary UI updates in the collection view even when providing new instances.

Note: Remember that isEqual(toDiffableObject:) should return false when you want to reload the cells in the corresponding section controller.

Diffing outside of IGListKit

If you want to use the diffing algorithm outside of IGListAdapter and UICollectionView, you can! The diffing algorithm was built with the flexibility to be used with any models that conform to IGListDiffable.

let result = ListDiff(oldArray: oldUsers, newArray: newUsers, .equality)

With this you have all of the deletes, reloads, moves, and inserts! There’s even a function to generate NSIndexPath results.

Advanced Features

Working Range

A working range is a range of section controllers who aren’t yet visible, but are near the screen. Section controllers are notified of their entrance and exit to this range. This concept lets your section controllers prepare content before they come on screen (e.g. download images).

The IGListAdapter must be initialized with a range value in order to work. This value is a multiple of the visible height or width, depending on the scroll-direction.

let adapter = ListAdapter(updater: ListAdapterUpdater(),
                   viewController: self,
                 workingRangeSize: 1) // 1 before/after visible objects

working-range

You can set the weak workingRangeDelegate on a section controller to receive events.

Supplementary Views

Adding supplementary views to section controllers is as simple as setting the (weak) supplementaryViewSource and implementing the IGListSupplementaryViewSource protocol. This protocol works nearly the same as returning and configuring cells.

Display Delegate

Section controllers can set the weak displayDelegate delegate to an object, including self, to receive display events about a section controller and individual cells.

Custom Updaters

The default IGListAdapterUpdater should handle any UICollectionView update that you need. However, if you find the functionality lacking, or want to perform updates in a very specific way, you can create an object that conforms to the IGListUpdatingDelegate protocol and initialize a new IGListAdapter with it.

Check out the updater IGListReloadDataUpdater (used in unit tests) for an example.