📜 ⬆️ ⬇️

UICollectionView or dancing with wolves

The dream


UICollectionView is a UIKit class that appeared in iOS 6. Strictly speaking, this is a class that allows you to display a collection of items on the screen. The structure of the collection is absolutely arbitrary, but usually the UICollectionView is used for all grid-like controls with cells, headers and footers. Understanding how abstract this class is, Apple developers created a powerful mechanism for creating any layouts. By and large, even UITableView is a concrete implementation of UICollectionView. The possibilities of this class are, in a sense, fantastic. But this article is not about this.

The Achilles heel of Apple developers is a constant desire to make KFOR that will work "automagically." Just do this and that, and the “will do the right thing” class. Unfortunately, this does not always work. And UICollectionView is a prime example. From the release in iOS 6 to today (iOS 7.0.4), the class contains a fairly large number of bugs that are very difficult and unpleasant to deal with. You have to guess what is happening "under the hood", and by typing the UICollectionView to work as it should. The number of acquired crutches has already reached such a size that I decided to share known bugs and solutions found.

To whom it is interesting - you are welcome under the cat.

Reality


Before starting to describe the bugs, I will try to describe the “ideal scenario” in which the UICollectionView works stably, without any crutches. As a rule, this is a UICollectionView, in which there are no headers and footers, only cells. When the controller has loaded into memory (viewDidLoad :), the UICollectionView -reloadData method is called. Animated deletions, moves, and inserts are not done. Here is such an ideal scenario for a crutch-free UICollectionView. As you can see, it is quite limited.
')
Immediately make a reservation that you can not meet the bugs from the list that I give below. Each bug was in specific conditions, which you may not have. And each solution also helped in my particular case, there is no guarantee that it will help you, and that it will continue to work in the next iOS releases. Many of the crutches will look ugly, but what kind of crutch is not ugly? However, if something helps, I will be happy =). Go!

iOS 6 + iOS 7


1. You cannot insert the first UICollectionViewCell into sections. Also, you cannot delete the last remaining UICollectionViewCell. Attempting to do this leads to crash in debug builds, and unpredictable behavior in release builds:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 1

Comment. The most famous bug with UICollectionView. The radar is open on the bug tracker Apple. The most popular and stable solution is to call reloadData. Sometimes it can help to call the insertItemsAtIndexPaths method: in a try-catch block (yes, the leg is ruthlessly shot off).

@try { [self.collectionView insertItemsAtIndexPaths:indexPaths]; } @catch (NSException *exception) {} 

Another solution, which probably should not be used, but worth mentioning is to call the reloadData inside the block's performBatchUpdates.

 [self.collectionView performBatchUpdates:^{ [self.collectionView reloadData]; } completion:nil]; 

2. Attempting to perform two animation operations at the same time without performing the block’s BatchUpdates will cause the application to crash.

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. This is the number of sections of the number of sections of the number of sections. deleted). '

Comment. This item may seem insignificant, but in fact it is very important. 80% of my personal problems with the UICollectionView arose when it was necessary to group several animations. In particular, this happens due to NSFetchedResultsController and updates in the CoreData database. There is an open source solution from Ash Furrow , which allows you to work correctly with updates in CoreData.

3. You can not update sections and cells in one performBatchUpdates block. By updating is meant insertion or deletion.

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. This is the number of sections of the number of sections of the number of sections. deleted). '

Comment. A rather strange problem; two consecutive performBatchUpdates blocks help; in the first block, the sections are updated, in the second, the cells.

4. You cannot call performBatchUpdates immediately after calling reloadData.

attempt to delete section 0, but there are only sections before the update

Comment. It seems that the reloadData in the UICollectionView works asynchronously, unlike the UITableView.

Update 05/02/2014 There is a crutch method to call reloadData synchronously. To do this, you need to create a new UICollectionViewLayout object and call the method

 [collectionView setCollectionViewLayout:layout animated:NO]; 

The method inside will call private synchronous reloadData. The solution works ONLY if there were already elements / cells in the UICollectionView. If there were no cells, nothing happens for the first time, the second time there is a crash. Thanks to PALKOVNIK for the suggested hack.

Update 03/03/2014 The second crutch method is to synchronously update the UICollectionView:

 [collection.collectionView reloadData]; [collection.collectionView performBatchUpdates:nil completion:nil]; 


5. When using UICollectionViewFlowLayout, if the headerSize or footerSize is non-zero, the collectionView: viewForSupplementaryElementOfKind: atIndexPath: method should not return nil, otherwise crash.

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the view returned from -collectionView: viewForSupplementaryElementOfKind: atIndexPath (UICollectionElementKindSectionFooter, <NSIndexPath: 0x8e55df0> {length = 2, path = 0 - 0}) was not retrieved by calling -dequeueReusableSupplementaryViewOfKind: withReuseIdentifier: forIndexPath: or is nil ((null)) '

Comment. If you have a UICollectionView in which the header or footer may be missing, you need to define a delegate method

  -(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)sectionNumber 

And if you have a missing header in this section, return CGSizeZero.

iOS 6


1. UICollectionViewFLowLayout. If you have a section without cells, and you have not defined a delegate collectionView: layout method: referenceSizeForHeaderInSection: you are not lucky, crash runtime.

*** Assertion failure in - [UICollectionViewData indexPathForItemAtGlobalIndex:]
global index path request for global index 805306368 when there are only 0 items in the collection view

Comment. You must define the collectionView: layout: referenceSizeForHeaderInSection: method, and if there are no cells in the section, return CGSizeZero.

2. Sometimes, after a call to reloadData, old cells remain in place, despite the fact that the UICollectionViewDatasource methods clearly return that they are not. Especially often appears when the UICollectionView is closed by the keyboard, or by another UIView. Unfortunately there is no solution.

iOS 7


1. In case the cell is the last in the section, as well as the presence of footers in the UICollectionView, calling the moveItemAtIndexPath: toIndexPath: method for this cell in another section will result in a drop

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no UICollectionViewLayoutAttributes instance for -layoutAttributesForSupplementaryElementOfKind: UICollectionElementKindSectionFooter at path <NSIndexPath: 0xc054730> 0.

Comment. This problem is very similar to inserting and deleting the first item in the section, so the solution is the same, calling reloadData if the item is the last one.

2. Deleting a section at a time when the UICollectionView is not displayed on the screen results in a fall.

*** Assertion failure in - [UICollectionViewData validateLayoutInRect:], /SourceCache/UIKit_Sim/UIKit-2903.23/UICollectionViewData.m.7341
2014-01-10 17: 34: 55.198 SMS-Bank [47090: 70b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'It doesn’t: <NSIndexPath: 0xc000000000000056> {length = 2, path = 1 - 0} '

Comment At the moment when the UICollectionView disappears, we reset the delegate and the datasource.

 self.collectionView.delegate = nil; self.collectionView.dataSource = nil; 


3. iOS simulator, iPad 2 and iPad 3 do not use reuse for UICollectionViewCell. Also, a reuse may stop working if Accessibility shortcuts is enabled.

Comment.

Read more on the stack: http://stackoverflow.com/questions/19276509/uicollectionview-do-not-reuse-cells
Radar: http://openradar.appspot.com/15357491

The end


The list can be far from complete, unfortunately the UICollectionView is rather unstable. I hope someday Apple developers will bring it to mind. In the meantime, I can advise the library , which was created for convenient work with UICollectionView.

Actually, it was in the process of writing this library that the author found most of the bugs described in the article. The purpose of this framework has never been fix bugs iOS SDK, but today the crutches added for iOS 6 and iOS 7, have become one of the important features. The ability to forget about a nightmare called UICollectionView + NSInternalInconsistencyException is priceless. If you have alternative solutions, or information about other UICollectionView bugs - share it in the comments!

Thanks for attention!

Source: https://habr.com/ru/post/211144/


All Articles