ReactiveCollectionsKit

repository·main·Indexed 41 days ago

https://github.com/jessesquires/reactivecollectionskit

A data-driven library for building reactive, diffable collections in iOS using UICollectionView. It provides a declarative architecture using CollectionViewModel and CellViewModel to eliminate manual data source and delegate implementation, handling both identity and equality for automatic updates. Requires iOS 16.0+, Swift 6.0+, and Xcode 26.0+.

Tokens
1.8K
Snippets
6
Records
8
Agent score
2%

What's inside ReactiveCollectionsKit

  1. How CellViewModel and CollectionViewModel work together

    main

    ReactiveCollectionsKit uses a uni-directional data flow pattern based on two core components:

    1. CellViewModel: The "atomic" component. It encapsulates all data, configuration, interaction, and registration for a single cell. It is responsible for defining how a cell is configured and how it handles interaction events.
    2. CollectionViewModel: The immutable representation of the entire collection structure. It acts as the "driver" for the collection.

    The Workflow:

    1. Fetch or update your underlying data models.
    2. Generate CellViewModel objects and a complete CollectionViewModel from that data.
    3. Set the CollectionViewModel on the CollectionViewDriver.
    4. The driver performs a diff against the previously set model and updates the UICollectionView automatically.
    # Example workflow concept
    # 1. Data -> 2. CellViewModels + CollectionViewModel -> 3. Driver -> 4. UI
  2. Understand diffing with identity and equality

    main

    To perform efficient diffing, the library distinguishes between identity (who an object is) and equality (what an object looks like). This is implemented via the DiffableViewModel protocol.

    • Identity (Identifiable): A permanent and unique identifier (the id property). It answers "who is this?" and is used to determine if an item is present, moved, added, or deleted.
    • Equality (Hashable/Equatable): Ephemeral traits or properties that change over time. It answers "is this the most up-to-date version?" and is used to determine if a specific item needs to be reloaded or refreshed.

    Note on UIKit Compatibility: UICollectionViewDiffableDataSource only handles identity (structure changes). ReactiveCollectionsKit handles both identity and equality (content changes), ensuring items are reloaded when their properties change.

    typealias UniqueIdentifier = AnyHashable
    
    protocol DiffableViewModel: Identifiable, Hashable {
        var id: UniqueIdentifier { get }
    }
  3. Install ReactiveCollectionsKit via Swift Package Manager

    main

    Add the following dependency to your Package.swift file to integrate ReactiveCollectionsKit into your project. You can also add the package directly through the Xcode interface.

    dependencies: [
        .package(url: "https://github.com/jessesquires/ReactiveCollectionsKit.git", from: "0.1.0")
    ]
  4. Handle mixed data types using type-erasure

    main

    Because CellViewModel uses generics for type-safe cell configuration, you cannot directly put different types of CellViewModel into a single array. To handle heterogeneous data (mixed cell types) in a section, you must use type-erasure to convert specific view models into AnyCellViewModel using the eraseToAnyViewModel() method.

    If your section contains only one type of cell, SectionViewModel provides generic initializers that handle this erasure for you automatically.

    // When using mixed data types, erase them to AnyCellViewModel
    let people = [Person]()
    
    let cellViewModels = people.map {
        PersonCellViewModel($0).eraseToAnyViewModel()
    }
  5. Avoid manual UICollectionView updates when using ReactiveCollectionsKit

    main

    ReactiveCollectionsKit manages the state and updates of your UICollectionView automatically via a declarative, data-driven approach. To prevent conflicts with the library's internal diffing and update logic, do not manually call the following UICollectionView APIs:

    • reloadData()
    • reconfigureItems(at:)
    • reloadSections(_:)
    • reloadItems(at:)
    • performBatchUpdates(_:completion:)
    • Any UICollectionViewDataSource methods
    • Any UICollectionViewDelegate methods
  6. Quickstart: Build a simple list with CollectionViewDriver

    main

    To build a collection view, you define your data using a hierarchy of view models: CollectionViewModel contains SectionViewModels, which in turn contain cell view models. You then initialize a CollectionViewDriver with your UICollectionView and these view models. The driver handles all diffing, registration, and updates automatically. When your data changes, simply call driver.update(viewModel:) with a new view model instance.

    let models = [/* array of some data models */]
    
    // create cell view models from the data models
    let cellViewModels = models.map {
        MyCellViewModel($0)
    }
    
    // create the sections with cells
    let section = SectionViewModel(id: "my_section", cells: cellViewModels)
    
    // create the collection with sections
    let collectionViewModel = CollectionViewModel(sections: [section])
    
    // initialize the driver with the view model and other components
    let driver = CollectionViewDriver(
        view: collectionView,
        viewModel: collectionViewModel,
        emptyViewProvider: provider,
        cellEventCoordinator: coordinator
    )
    
    // the collection view is updated and animated automatically
    
    // when the models change, generate a new view model (like above)
    let updated = CollectionViewModel(sections: [/* updated items and sections */])
    driver.update(viewModel: updated)
  7. Implement the CellViewModel protocol

    main

    The CellViewModel is the fundamental component for defining cell behavior. It inherits from DiffableViewModel and ViewRegistrationProvider. It uses an associatedtype CellType to provide type-safe configuration without manual casting.

    When implementing CellViewModel, you define how to configure your specific UICollectionViewCell type.

    protocol CellViewModel: DiffableViewModel, ViewRegistrationProvider {
        associatedtype CellType: UICollectionViewCell
    
        func configure(cell: CellType)
    
        // other members...
    }