Mobile Development 17 min read

Simplify Complex UICollectionView Layouts with IGListKit

This article explains how IGListKit can replace cumbersome UICollectionView data‑source code with a data‑driven framework, showing step‑by‑step Swift examples for building a two‑column video grid, handling ads, animations, diffing, and advanced section controllers.

Baixing.com Technical Team
Baixing.com Technical Team
Baixing.com Technical Team
Simplify Complex UICollectionView Layouts with IGListKit

UICollectionView is the most commonly used list component on iOS, providing a flexible API for creating, managing, and grouping subviews. When view types become highly varied or hierarchical, native interfaces can feel clumsy.

We will use IGListKit to rebuild a simple video‑square page: two columns of video thumbnails, each showing author information, and tapping a thumbnail navigates to a detail page.

Problem

You are a short‑video entrepreneur needing a video plaza page with two‑column thumbnails, each thumbnail displaying the author at the bottom. As an experienced iOS developer you can finish this page in thirty minutes.

The interface is composed of a UICollectionView that handles layout, view management, and user interaction via UICollectionViewDataSource and UICollectionViewDelegate.

After a stable first version, the second version requires inserting full‑width ad cells randomly, smooth transition animations on each refresh, and the whole feature must be completed within a workday.

All data are forced into a single Array, requiring an enum to unify different data types.

Every place that needs to act differently based on data type must expand all possible switch branches.

Each new data type forces additional switch cases, increasing workload and error risk. How can we abstract this repetitive work?

IGListKit

IGListKit is Instagram’s data‑driven framework for UICollectionView, allowing developers to create faster and more flexible list pages. Instead of directly controlling UICollectionView, IGListKit turns the abstract section concept of UICollectionViewDataSource into concrete section controllers, and ListAdapter dispatches events to those controllers. When data changes, calling

performUpdates(animated:completion:)

handles rendering and animation automatically.

Let’s re‑implement the above requirement with IGListKit.

<code/* Swift */
private let dataSource: DataSource = .init()

lazy var listAdapter: ListAdapter = {
    let listAdapter = ListAdapter(updater: ListAdapterUpdater(), viewController: self)
    listAdapter.dataSource = dataSource
    return listAdapter
}()

override func viewDidLoad() {
    super.viewDidLoad()
    listAdapter.collectionView = collectionView
}
</code>

IGListKit replaces UICollectionViewDataSource and UICollectionViewDelegate with ListAdapter, and adds ListAdapterUpdater to rewrite refresh calls.

<code/* Swift */
private class DataSource: ListAdapterDataSource {
    var sections: [SectionItem] = []

    // Objects for each section
    func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
        return sections
    }

    // Section controller for each object
    func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
        return (object as? SectionControllerBounded)?.listAdapter(listAdapter, sectionControntrollerFor: self) ?? ListSectionController()
    }

    // Empty view when no data
    func emptyView(for listAdapter: ListAdapter) -> UIView? { return nil }
}
</code>

ListAdapterDataSource supplies data and section controllers. We can iterate supported data types and return the appropriate ListSectionController, or use a protocol to delegate creation.

<code/* Swift */
protocol SectionControllerBounded {
    func listAdapter(_ listAdapter: ListAdapter, sectionControntrollerFor dataSource: ListAdapterDataSource) -> ListSectionController
}

typealias SectionItem = ListDiffable & SectionControllerBounded
</code>

ListSectionController

ListSectionController renders a block of the list, containing an arbitrary number of cells and optional decorative views. It provides methods for cell creation and auxiliary information.

<code/* Swift: 运营广告 */
class ADSectionController: ListSectionController {
    var sectionItem: ADSectionItem!

    override func cellForItem(at index: Int) -> UICollectionViewCell {
        let cell = collectionContext!.dequeueReusableCell(of: ADCell.self, for: self, at: index) as! ADCell
        cell.set(item: sectionItem.item)
        return cell
    }

    override func didUpdate(to object: Any) {
        sectionItem = object as! ADSectionItem
        super.didUpdate(to: object)
    }
}
</code>

We can set insets, line spacing, and inter‑item spacing in the controller’s initializer.

<code/* Swift */
override init() {
    super.init()
    inset = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
    minimumLineSpacing = 8
    minimumInteritemSpacing = 8
}
</code>

ListBindingSectionController

If a view model can be split down to the cell level, ListBindingSectionController simplifies cell configuration.

<code/* Swift: 视频区块 */
class VideoSectionController: ListBindingSectionController<ChunkOfVideos>, ListBindingSectionControllerDataSource {
    override init() { super.init(); dataSource = self }

    func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, viewModelsFor object: Any) -> [ListDiffable] {
        guard let chunk = object as? ChunkOfVideos else { return [] }
        return chunk.videos
    }

    func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, cellForViewModel viewModel: Any, at index: Int) -> UICollectionViewCell & ListBindable {
        return collectionContext!.dequeueReusableCell(of: VideoCell.self, for: self, at: index) as! VideoCell
    }

    func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, sizeForViewModel viewModel: Any, at index: Int) -> CGSize {
        return fixedCellSize
    }
}
</code>

ListBindingSectionController automatically manages diffing and animation based on view models.

ListStackedSectionController

When a section contains additional hierarchy, ListStackedSectionController can compose multiple child controllers.

<code/* Swift: 视频、广告混合区块 */
let sectionController = ListStackedSectionController(sectionControllers: [VideoSectionController(), ADSectionController()])
</code>

This separates video and ad logic into independent controllers while presenting them as a single stacked section.

Animation

IGListKit includes a diff algorithm; any type conforming to ListDiffable can be diffed. Updating the data source and calling

performUpdates(animated:completion:)

automatically generates the appropriate key‑frame animations.

<code/* Swift */
public protocol ListDiffable: NSObjectProtocol {
    // Identify whether two diffables represent the same data
    func diffIdentifier() -> NSObjectProtocol
    // Determine whether the same data has been updated
    func isEqual(toDiffableObject object: ListDiffable?) -> Bool
}
</code>

Pitfalls and Tips

Delegate Proxy

IGListKit occupies UICollectionView’s delegate property. If you also need to intercept view‑controller events, you can use ListAdapter’s

scrollViewDelegate

and

collectionViewDelegate

. When either is set, ListAdapter creates an NSProxy that forwards intercepted selectors (e.g., cell selection, layout sizing, scroll events) to the appropriate delegate.

<code/* Objective‑C: IGListAdapterProxy.m */
static BOOL isInterceptedSelector(SEL sel) {
    return (
        sel == @selector(collectionView:didSelectItemAtIndexPath:) ||
        sel == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) ||
        sel == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) ||
        sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) ||
        sel == @selector(collectionView:layout:insetForSectionAtIndex:) ||
        sel == @selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:) ||
        sel == @selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:) ||
        sel == @selector(scrollViewDidScroll:) ||
        sel == @selector(scrollViewWillBeginDragging:) ||
        sel == @selector(scrollViewDidEndDragging:willDecelerate:) ||
        sel == @selector(scrollViewDidEndDecelerating:)
    );
}
</code>

Section Backgrounds

iOS does not provide built‑in section background support. You can use UICollectionViewLayout’s decoration view concept to insert a background view at the bottom of a section and let the delegate provide layout attributes.

<code/* Swift */
class Layout: UICollectionViewFlowLayout {
    override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        let superAttrs = super.layoutAttributesForDecorationView(ofKind: elementKind, at: indexPath)
        guard let delegate = collectionView?.delegate,
              delegate.responds(to: #selector(Delegate.layoutAttributesForDecorationView(ofKind:at:overriding:))) else { return superAttrs }
        return (delegate as AnyObject).layoutAttributesForDecorationView(ofKind: elementKind, at: indexPath, overriding: superAttrs)
    }
}

class Delegate: NSObject, UICollectionViewDelegate {
    weak var adapter: ListAdapter?
    @objc func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath, overriding layoutAttributes: UICollectionViewLayoutAttributes?) -> UICollectionViewLayoutAttributes? {
        // Return customized attributes based on section data
        return layoutAttributes
    }
}
</code>

Diffing Edge Cases

ListDiffable behaves like Hashable. If multiple items share the same

diffIdentifier

, IGListKit matches from the start of the old array and marks unmatched items as inserts or deletions. This can cause animation errors when many new items with identical identifiers are inserted at the top. A workaround is to reverse the diff order inside

didUpdate(to:)

and then re‑reverse the result before applying updates.

Conclusion

IGListKit effectively solves the horizontal extensibility problem of UICollectionView data sources and manages transition animations automatically. Although wrapping a core UI component adds a layer of abstraction, the benefits in modularity and animation handling outweigh the added complexity, provided developers weigh the trade‑offs before adopting the framework.

animationiOSSwiftUICollectionViewIGListKitdiffingListAdapter
Baixing.com Technical Team
Written by

Baixing.com Technical Team

A collection of the Baixing.com tech team's insights and learnings, featuring one weekly technical article worth following.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.