IGListSectionController's didUpdate and cellForItem always re-called, even though isEqual == true

760 views Asked by At

Trying to implement the IGListKit library, I'm running into the issue that my cells are updated unnecessarily. I'm using a singleton adapter.dataSource with one section per row in the table.

Minimum example:

import IGListKit

class ContentItem: ListDiffable {
  weak var item: Content?
  weak var section: ContentSectionController?

  func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
    return true
  }

  init(item: Content?) {
    self.item = item
  }
}

class ContentSectionController: ListSectionController {
  weak var object: ContentItem?
  override func didUpdate(to object: Any) {
    self.object = object as? ContentItem
    self.object?.section = self
    // should only be called on updates
  }

  override func sizeForItem(at index: Int) -> CGSize {
    guard let content = object?.item else {
      return CGSize(width: 0, height: 0)
    }
    // calculate height
  }

  override func cellForItem(at index: Int) -> UICollectionViewCell {
    let cell = collectionContext!.dequeueReusableCellFromStoryboard(withIdentifier: "ContentCell", for: self, at: index)

    (cell as? ContentCell)?.item = object // didSet will update cell
    return cell
  }

  override init() {
    super.init()
    self.workingRangeDelegate = self
  }
}

extension ContentSectionController: ListWorkingRangeDelegate {
  func listAdapter(_ listAdapter: ListAdapter, sectionControllerWillEnterWorkingRange sectionController: ListSectionController) {
    // prepare
  }

  func listAdapter(_ listAdapter: ListAdapter, sectionControllerDidExitWorkingRange sectionController: ListSectionController) {
    return
  }
}

class ContentDataSource: NSObject {
  static let sharedInstance = ContentDataSource()

  var items: [ContentItem] {
    return Content.displayItems.map { ContentItem(item: $0) }
  }
}

extension ContentDataSource: ListAdapterDataSource {
  func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
    return items
  }

  func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
    return ContentSectionController()
  }

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

/// VC ///

class ContentViewController: UIViewController {
  @IBOutlet weak var collectionView: UICollectionView!

  override func viewDidLoad() {
    super.viewDidLoad()

    let updater = ListAdapterUpdater()
    adapter = ListAdapter(updater: updater, viewController: self, workingRangeSize: 2)
    adapter.collectionView = collectionView
    adapter.dataSource = ContentDataSource.sharedInstance
  }
  var adapter: ListAdapter!
  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    adapter.performUpdates(animated: true)
  }
  // ...
}

On every view appear I call adapter.performUpdates(animated: true), which should never update the cells since isEqual is overridden with true. Nonetheless, all cells' didUpdate is triggered, calling cellForItem again too.

1

There are 1 answers

0
bkbkchoy On

IGListKit requires both diffIdentifier and isEqual to be implemented with the IGListDiffable protocol in order to compare the identity/equality of two objects. (You're missing the diff identifier in your model).

My understanding is that under the hood, ListKit checks to see if the two diff identifiers of the objects are equal, if they are THEN it moves on to comparing them with isEqual.

Resources: IGListKit Best Practices IGListDiffable Protocol Reference