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.
README and documentationIGListKit version: 3.1.0# 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>>
@MathieuWhite thanks for the report! So from the debug dump, I can see that
UICollectionViewFlowLayoutFooterViewIs 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.
@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.
@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:
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.
Thanks for the quick turnaround, @rnystrom!
Much appreciated.
Most helpful comment
@MathieuWhite your bug is in dynamically changing if a supplementary view exists or not in
supportedElementKinds.UICollectionViewFlowLayoutisn't meant to dynamically insert/remove supplementary views w/out callinginvalidateLayout. 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
isLastSectioncheck to the size and use a non-zero size.I verified that this works. A little bit of a hack, but since this is a limitation of
UICollectionViewFlowLayoutI'm going to go ahead and close this.