IGListKit

repository·main·Indexed 11 days ago

https://github.com/instagram/iglistkit

A data-driven UICollectionView framework developed by Instagram for building high-performance lists. It features a decoupled diffing algorithm to automate updates, reducing the need for manual batch updates or full reloads. Written in Objective-C with full Swift interop support, it requires iOS 11.0+, tvOS 11.0+, or macOS 10.13+.

Tokens
12.1K
Snippets
33
Records
52
Agent score
94%

What's inside IGListKit

  1. What is IGListKit

    main

    IGListKit is a data-driven UICollectionView framework designed to build fast and flexible lists. It is written in Objective-C but provides full support for Swift.

    Key features include:

    • Automatic updates: No need to manually call performBatchUpdates(_:completion:) or reloadData().
    • Decoupled Diffing: Uses a decoupled diffing algorithm to calculate changes.
    • Reusable Architecture: Encourages a better system of reusable cells and components.
    • Heterogeneous Lists: Easily create lists containing multiple different data types.
    • Extensibility: Designed with an extensible API.
    • Customization: Allows for custom diffing behavior for data models.
  2. Overview of IGListKit

    main
    IGListKit is a data-driven UICollectionView framework designed for building fast and flexible lists. It provides a decoupled diffing algorithm that allows you to create collections with multiple data types without manually calling performBatchUpdates(_:, completion:) or reloadData(). It is written in Objective-C with full Swift interop support.
  3. Understand the scope and goals of IGListKit

    main

    The core goal of IGListKit is to enable the construction of fast, stable, and data-driven lists in iOS applications.

    In-scope features include:

    • Integrations with UICollectionView and UITableView.
    • Data and state management.
    • Diffing algorithms.

    Out-of-scope features (do not expect built-in support for these):

    • Advanced or custom UICollectionViewLayouts.
    • Sizing and layout logic (e.g., Auto Layout or estimated sizes).
    • Render and display pipelines.
    • Third-party integrations.
  4. Implement ListDiffable for models

    main

    To use models with IGListKit, they must conform to the ListDiffable protocol. This requires implementing two methods:

    1. diffIdentifier(): Returns a unique identifier for the object (e.g., a database ID or a combination of unique fields). This allows IGListKit to track the identity of an object across updates.
    2. isEqual(toDiffableObject:): Compares the content of two objects with the same identifier. If this returns false, the UI will be updated to reflect the changes.

    Important Requirements:

    • If two models have the same diffIdentifier, they must be considered equal by isEqual(toDiffableObject:) if their content hasn't changed.
    • For ListBindingSectionController, a good equality check is critical; whenever a property changes, isEqual must return false to trigger a cell refresh.
    final class UserViewModel: ListDiffable {
      let username: String
      let timestamp: String
    
      init(username: String, timestamp: String) {
        self.username = username
        self.timestamp = timestamp
      }
    
      func diffIdentifier() -> NSObjectProtocol {
        return "user" as NSObjectProtocol
      }
    
      func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
        guard let object = object as? UserViewModel else { return false }
        return username == object.username && timestamp == object.timestamp
      }
    }
  5. Use Working Range to pre-fetch content

    main

    A working range allows section controllers to prepare content (like downloading images) before they become visible on screen.

    To enable this, initialize your ListAdapter with a workingRangeSize. This value represents a multiple of the visible height or width (e.g., 1 means one screen's worth of content before and after the visible area). Section controllers can then use a workingRangeDelegate to receive entrance and exit events.

    let adapter = ListAdapter(updater: ListAdapterUpdater(),
                       viewController: self,
                     workingRangeSize: 1) // 1 before/after visible objects
  6. Configure Supplementary Views and Display Delegates

    main

    Section controllers support two key extension mechanisms:

    • Supplementary Views: Implement the IGListSupplementaryViewSource protocol and set it as the supplementaryViewSource on your section controller to manage headers or footers.
    • Display Delegate: Set a displayDelegate on your section controller to receive lifecycle events for the section controller and its individual cells (e.g., when they enter or leave the screen).
  7. How to use Core Data with IGListKit

    main

    Because Core Data uses mutable NSManagedObjects passed by reference, they cannot be used directly as ListDiffable objects. IGListKit requires immutable models to correctly calculate diffs and animate the UICollectionView.

    To integrate them, you must use a ViewModel (or a token object) as an immutable proxy for your Core Data objects. The workflow is:

    1. Retrieve Core Data objects (e.g., via NSFetchedResultsController).
    2. Transform those objects into immutable ViewModels that implement ListDiffable.
    3. Track changes to Core Data and trigger adapter.performUpdates(animated:) when the underlying data changes.
  8. Use `IGListBindingSectionController` for cell diffing (v3.x)

    main
    If you were previously using IGListDiff(...) manually inside a section controller to compute diffs for cells, you should migrate to IGListBindingSectionController. This class provides a tested and elegant API that wraps this diffing behavior automatically.
  9. Implement ListDiffable for efficient updates

    main

    To enable IGListKit's diffing algorithm to identify inserts, deletes, updates, and moves, your data models must conform to ListDiffable.

    1. diffIdentifier(): Return a unique identifier (e.g., a primary key) that represents the identity of the object. This identifier must never change for the life of the object.
    2. isEqual(toDiffableObject:): Return true if the content of the object is the same as another, and false if the content has changed. Returning false triggers a cell reload.

    Important: Always use immutable models. If you mutate an existing object instead of providing a new instance, the diffing algorithm may fail to detect changes because the old and new instances will appear identical.

    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
      }
    }
  10. Use ListBindingSectionController for animated cell updates

    main

    A ListBindingSectionController is a specialized section controller that manages a collection of view models derived from a single top-level model. It automates the process of mapping models to cells and provides animated, one-way updates.

    Workflow

    1. Top-level Model: The section controller receives one main model (e.g., a Post).
    2. Decomposition: The controller's data source decomposes that main model into an array of smaller 'view models' (e.g., UserViewModel, ImageViewModel, Comment).
    3. Binding: The controller maps each view model to a specific UICollectionViewCell type and provides its size.

    Implementation Steps

    Subclass ListBindingSectionController<T> (where T is your top-level model) and conform to ListBindingSectionControllerDataSource. You must implement three methods:

    • sectionController(_:viewModelsFor:): Transform the top-level model into an array of ListDiffable view models.
    • sectionController(_:sizeForViewModel:at:): Return the CGSize for each view model type.
    • sectionController(_:cellForViewModel:at:): Dequeue and return the appropriate cell for each view model type.
    final class PostSectionController: ListBindingSectionController<Post>, ListBindingSectionControllerDataSource {
      override init() {
        super.init()
        dataSource = self
      }
    }