Instagram / IGListKit

A data-driven UICollectionView framework for building fast and flexible lists.
https://instagram.github.io/IGListKit/
MIT License
12.87k stars 1.54k forks source link

Assertion failure in -[UICollectionViewData layoutAttributesForSupplementaryElementOfKind:atIndexPath:] #920

Closed MathieuWhite closed 7 years ago

MathieuWhite commented 7 years ago

Hey guys,

I just came across a crash while trying to work with supplementary views in a section controller using the IGListSupplementaryViewSource protocol. Nothing fancy going on with the collection view layout, just using the default UICollectionViewFlowLayout on the collection view. See the assertion failure below:

2017-08-31 13:26:17.194 IGListKitExamples[2181:15665308] *** Assertion failure in -[UICollectionViewData layoutAttributesForSupplementaryElementOfKind:atIndexPath:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.7.47/UICollectionViewData.m:994
no UICollectionViewLayoutAttributes instance for -layoutAttributesForSupplementaryElementOfKind: UICollectionElementKindSectionFooter at path <NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}
(null)

To provide some context, the collection view is displaying a list of items using an ItemSectionController. This is the only section controller type being returned to the list adapter. There is a search field in the navigation bar, and text input is used to filter the results and sort by relevance. The section controller conforms to IGListSupplementaryViewSource, and returns a supplementary view of kind UICollectionElementKindSectionFooter if self.isLastSection is YES.

So this means you would see a list of results, and below the last cell a supplementary view is displayed with a label saying there are no more results. Note: Before switching over to using a supplementary view for this, it was set up to be its own section controller.

I thought I would checkout the IGListKitExamples to verify if I could reproduce the same issue. The perfect demo to test is the Search Autocomplete example provided. It's very similar to our app. I setup a new supplementary view for a footer, and made LabelSectionController conform to ListSupplementaryViewSource. At this point the crash is not reproducible, so I continued to investigate. I noticed the SearchViewController always returns a SearchSectionController which contains the search bar. So I got rid of the SearchSectionController and used a UISearchBar in the navigation bar. Now, the issue is reproducible.

So it seems that if the section controller that contains a supplementary view is moving to the first index path, it crashes. In the example app, the SearchSectionController is always the first index path so this doesn't crash. In fact, I checked this theory against our app and if I returned a different section controller as the first index path, it does not crash.

New issue checklist

General information

Debug information

# Please include debug logs using the following lldb command:
po [IGListDebugger dump]
IGListAdapter 0x608000141ef0:
  Updater type: IGListAdapterUpdater
  Data source: <IGListKitExamples.SearchViewController: 0x7fe1f4502e20>
  Collection view delegate: (null)
  Scroll view delegate: (null)
  Is in update block: No
  View controller: <IGListKitExamples.SearchViewController: 0x7fe1f4502e20>
  Is prefetching enabled: No
  Registered cell classes:
  {(
    IGListKitExamples.LabelCell
)}
  Registered supplementary view nib names:
  {(
    FooterView
)}
  IGListAdapterUpdater instance 0x6080000d6dc0:
    Moves as deletes+inserts: No
    Allows background reloading: Yes
    Has queued reload data: No
    Queued update is animated: Yes
    State: Executed batch update block
    Batch update data:
      Insert sections: <_NSCachedIndexSet: 0x60800003c9e0>(no indexes)
      Delete sections: <_NSCachedIndexSet: 0x60800003c9e0>(no indexes)
      Move from section 3 to 0
      Move from section 2 to 1
      Move from section 1 to 2
      Move from section 0 to 3
  Section map details:
    Object and section controller at section: 0:
      tacos
      <IGListKitExamples.LabelSectionController: 0x6080001331a0>
    Object and section controller at section: 1:
      viral
      <IGListKitExamples.LabelSectionController: 0x608000133100>
    Object and section controller at section: 2:
      skateboard
      <IGListKitExamples.LabelSectionController: 0x608000133060>
    Object and section controller at section: 3:
      Humblebrag
      <IGListKitExamples.LabelSectionController: 0x608000132fc0>
  Previous section map details:
    Object and section controller at section: 0:
      Humblebrag
      <IGListKitExamples.LabelSectionController: 0x608000132fc0>
    Object and section controller at section: 1:
      skateboard
      <IGListKitExamples.LabelSectionController: 0x608000133060>
    Object and section controller at section: 2:
      viral
      <IGListKitExamples.LabelSectionController: 0x608000133100>
    Object and section controller at section: 3:
      tacos
      <IGListKitExamples.LabelSectionController: 0x6080001331a0>
  Collection view details:
    Class: UICollectionView, instance: 0x7fe1f5846400
    Data source: <IGListAdapter: 0x608000141ef0>
    Delegate: <IGListAdapter: 0x608000141ef0>
    Layout: <UICollectionViewFlowLayout: 0x7fe1f4502250>
    Frame: {{0, 0}, {375, 667}}, bounds: {{0, -64}, {375, 667}}
    Number of sections: 4
      1 items in section 0
      1 items in section 1
      1 items in section 2
      1 items in section 3
    Visible cell details:
      Visible cell at section 0, item 0:
      <IGListKitExamples.LabelCell: 0x7fe1f460aa60; baseClass = UICollectionViewCell; frame = (0 0; 375 55); text = 'Humblebrag'; layer = <CALayer: 0x60000023d980>>
      Visible cell at section 1, item 0:
      <IGListKitExamples.LabelCell: 0x7fe1f46070b0; baseClass = UICollectionViewCell; frame = (0 55; 375 55); text = 'skateboard'; layer = <CALayer: 0x60000023e2e0>>
      Visible cell at section 2, item 0:
      <IGListKitExamples.LabelCell: 0x7fe1f46150f0; baseClass = UICollectionViewCell; frame = (0 110; 375 55); text = 'viral'; layer = <CALayer: 0x60000023d360>>
      Visible cell at section 3, item 0:
      <IGListKitExamples.LabelCell: 0x7fe1f4615890; baseClass = UICollectionViewCell; frame = (0 165; 375 55); text = 'tacos'; layer = <CALayer: 0x60000023ed80>>
IGListAdapter 0x6080001413f0:
  Updater type: IGListAdapterUpdater
  Data source: <IGListKitExamples.DemosViewController: 0x7fe1f4700b50>
  Collection view delegate: (null)
  Scroll view delegate: (null)
  Is in update block: No
  View controller: <IGListKitExamples.DemosViewController: 0x7fe1f4700b50>
  Is prefetching enabled: No
  Registered cell classes:
  {(
    IGListKitExamples.LabelCell
)}
  IGListAdapterUpdater instance 0x6080000d9670:
    Moves as deletes+inserts: No
    Allows background reloading: Yes
    Has queued reload data: No
    Queued update is animated: Yes
    State: Idle
  Section map details:
    Object and section controller at section: 0:
      <IGListKitExamples.DemoItem: 0x60800009a4f0>
      <IGListKitExamples.DemoSectionController: 0x6080000f9200>
  Collection view details:
    Class: UICollectionView, instance: 0x7fe1f6019c00
    Data source: <IGListAdapter: 0x6080001413f0>
    Delegate: <IGListAdapter: 0x6080001413f0>
    Layout: <UICollectionViewFlowLayout: 0x7fe1f4707010>
    Frame: {{0, 0}, {375, 667}}, bounds: {{0, 0}, {375, 667}}
    Number of sections: 1
      1 items in section 0
    Visible cell details:
      Visible cell at section 0, item 0:
      <IGListKitExamples.LabelCell: 0x7fe1f4602500; baseClass = UICollectionViewCell; frame = (0 0; 375 55); text = 'Search Autocomplete'; layer = <CALayer: 0x60000023d440>>
rnystrom commented 7 years ago

@MathieuWhite thanks for the report! So from the debug dump, I can see that

Is that right?

Could you provide a zip of the example project setup that can repro the crash? That way I can look into it a little deeper. I'm unclear if this is an issue w/ IGListKit or some UIKit bug w/ UICollectionViewFlowLayout.

MathieuWhite commented 7 years ago

@rnystrom Yes, that is exactly the layout and supplementary view used.

Here is a zip of the example project setup used. You can launch the iOS Example. To reproduce this issue, type the letter 'T' in the search bar.

IGListKit-SupplementaryFooter.zip

rnystrom commented 7 years ago

@MathieuWhite your bug is in dynamically changing if a supplementary view exists or not in supportedElementKinds. UICollectionViewFlowLayout isn't meant to dynamically insert/remove supplementary views w/out calling invalidateLayout. A SO question that is exactly the same issue:

https://stackoverflow.com/questions/30153793/crash-on-collectionview-performbatchupdates-when-trying-to-hide-footer-header

One solution is to move the isLastSection check to the size and use a non-zero size.

func sizeForSupplementaryView(ofKind elementKind: String, at index: Int) -> CGSize {
    guard let width = collectionContext?.containerSize.width else {
        fatalError()
    }
    return CGSize(width: width, height: isLastSection ? 40.0 : 0.01)
}

I verified that this works. A little bit of a hack, but since this is a limitation of UICollectionViewFlowLayout I'm going to go ahead and close this.

MathieuWhite commented 7 years ago

Thanks for the quick turnaround, @rnystrom! Much appreciated.