A data-driven UICollectionView
framework for building fast and flexible lists.
| Main Features
---------|---------------
π
| Never call performBatchUpdates(_:, completion:)
or reloadData()
again
π | Better architecture with reusable cells and components
π | Create collections with multiple data types
π | Decoupled diffing algorithm
β
| Fully unit tested
π | Customize your diffing behavior for your models
π± | Simply UICollectionView
at its core
π | Extendable API
π¦ | Written in Objective-C with full Swift interop support
IGListKit
is built and maintained with β€οΈ by Instagram engineering, using the open source version for the Instagram app.
- Xcode 8.0+
- iOS 8.0+
- Interoperability with Swift 3.0+
The preferred installation method for IGListKit
is with CocoaPods. Simply add the following to your Podfile
:
# Latest release of IGListKit
pod 'IGListKit'
To integrate IGListKit
into your Xcode project using Carthage, specify it in your Cartfile
:
github "Instagram/IGListKit" ~> 1.0.0
You can also manually install the framework by dragging and dropping the IGListKit.xcodeproj
into your project or workspace.
After installing IGListKit
, creating a new list is really simple.
Creating a new section controller is very simple. You just subclass IGListSectionController
and conform to the IGListSectionType
protocol. Once you conform to IGListSectionType
, the compiler will make sure you implement all of the required methods.
Take a look at LabelSectionController for an example section controller that handles a String
and configures a single cell with a UILabel
.
class LabelSectionController: IGListSectionController, IGListSectionType {
// ...
}
After creating at least one section controller, you must create an IGListCollectionView
and IGListAdapter
.
let layout = UICollectionViewFlowLayout()
let collectionView = IGListCollectionView(frame: .zero, collectionViewLayout: layout)
let updater = IGListAdapterUpdater()
let adapter = IGListAdapter(updater: updater, viewController: self, workingRangeSize: 0)
adapter.collectionView = collectionView
Note: This example is done within a
UIViewController
and uses both a stockUICollectionViewFlowLayout
andIGListAdapterUpdater
. You can use your own layout and updater if you need advanced features!
The last step is the IGListAdapter
's data source and returning some data.
func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
// this can be anything!
return [ "Foo", "Bar", 42, "Biz" ]
}
func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController {
if object is String {
return LabelSectionController()
} else {
return NumberSectionController()
}
}
func emptyView(for listAdapter: IGListAdapter) -> UIView? {
return nil
}
You can return an array of any type of data, as long as it conforms to IGListDiffable
. We've included a default implementation for all objects, but adding your own implementation can unlock even better 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(_:)
.
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: IGListDiffable {
func diffIdentifier() -> NSObjectProtocol {
return primaryKey
}
func isEqual(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 unecessary UI updates in the collection view even when providing new instances.
Note: Remember that
isEqual(_:)
should returnfalse
when you want to reload the cells in the corresponding section controller.
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 = IGListDiff(oldUsers, newUsers, .equality)
With this you have all of the deletes, reloads, moves, and inserts! There's even a function to generate NSIndexPath
results.
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 = IGListAdapter(updater: IGListAdapterUpdater(),
viewController: self,
workingRangeSize: 1) // 1 before/after visible objects
You can set the weak workingRangeDelegate
on a section controller to receive events.
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.
Section controllers can set the weak displayDelegate
delegate to an object, including self
, to receive display events about a section controller and individual cells.
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.
You can find the docs here. Documentation is generated with jazzy and hosted on GitHub-Pages.
Please see the CONTRIBUTING file for how to help out. At Instagram we sync the open source version of IGListKit
almost daily, so we're always testing the latest changes. But that requires all changes be thoroughly tested follow our style guide.
IGListKit
is BSD-licensed. We also provide an additional patent grant.
The files in the /Example directory are licensed under a separate license as specified in each file; documentation is licensed CC-BY-4.0.