Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bad Access on [UICollectionView setCollectionViewLayout:animated:]

I'm getting a strange crash in my UICollectionView. The crashing UICollectionView is embedded in an UICollectionView cell of another UICollectionView.

I can't reproduce the issue, it seems to happen sometimes if the inner UICollectionView get's newly initialized because the outer CollectionView is reloading it's cells.

  com.apple.main-thread Crashed 0   libobjc.A.dylib     objc_msgSend + 9 1   UIKit   -[UICollectionViewData _setLayoutAttributes:atGlobalItemIndex:] + 60 2   UIKit   __45-[UICollectionViewData validateLayoutInRect:]_block_invoke_0 + 668 3   UIKit   -[UICollectionViewData validateLayoutInRect:] + 1408 4   UIKit   -[UICollectionViewData layoutAttributesForElementsInRect:] + 82 5   UIKit   -[UICollectionView setCollectionViewLayout:animated:] + 1644 6   MyApp   BSCTopnewsCollectionView.m line 52 -[BSCTopnewsCollectionView setupBSCTopnewsCollectionView] 7   MyApp   BSCTopnewsCollectionView.m line 27 -[BSCTopnewsCollectionView setWeakDelegatePointer:] 8   Myapp   BSCFrontPageViewController.m line 550 -[BSCFrontPageViewController collectionView:cellForItemAtIndexPath:] 9   UIKit   -[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:] + 252 10  UIKit   -[UICollectionView _updateVisibleCellsNow:] + 2672 11  UIKit   -[UICollectionView layoutSubviews] + 214 12  UIKit   -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 258 13  QuartzCore  -[CALayer layoutSublayers] + 214 14  QuartzCore  CA::Layer::layout_if_needed(CA::Transaction*) + 460 15  QuartzCore  CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 16 16  QuartzCore  CA::Context::commit_transaction(CA::Transaction*) + 238 17  QuartzCore  CA::Transaction::commit() + 316 18  QuartzCore  CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 60 19  CoreFoundation  __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 20 25  UIKit   UIApplicationMain + 1120 26  MyApp   main.m line 16 main    Exception Type:     EXC_BAD_ACCESS Code:     KERN_INVALID_ADDRESS at 0x158848  

What I'm doing in line 52 in setupBSCTopnewsCollectionView is

 BSCInfiniteLayout *infiniteLayout = [[BSCInfiniteLayout alloc] init];      (line 52) self.collectionView.collectionViewLayout = infiniteLayout; 



Edit: -[BSCFrontPageViewController collectionView:cellForItemAtIndexPath:]
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {     if([collectionView isEqual:self.collectionView])     {         if(indexPath.row == 0) // Header Cell         {             BSCTopnewsCollectionView *cell = [collectionView dequeueReusableCellWithReuseIdentifier:BSCHeaderReuseIdentifier forIndexPath:indexPath];             cell.dataSource = self;             cell.weakDelegatePointer = self;              self.topNewsCollectionView = cell;              return cell;         }         else         {             //create normal cells         }     }     else if ([collectionView isEqual:self.topNewsCollectionView.collectionView])     {         BSCTopNewsHeaderCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:BSCTopNewsCellReuseIdentifier forIndexPath:indexPath];         BSCNews *topnews = [self.topNews objectAtIndex:indexPath.row];          [cell setEntity:topnews];          return cell;     } } 

A few clarifications for the method calls there:

- (void)setWeakDelegatePointer:(BSCFrontPageViewController *)weakDelegatePointer {     _weakDelegatePointer = weakDelegatePointer;      [self setupBSCTopnewsCollectionView];     [self.collectionView reloadData]; }  - (void)setupBSCTopnewsCollectionView {     self.collectionView.delegate = self.weakDelegatePointer;     self.collectionView.dataSource = self.weakDelegatePointer;      BSCInfiniteLayout *infiniteLayout = [[BSCInfiniteLayout alloc] init];       infiniteLayout.delegate = self;      // Setup Layout     self.collectionView.collectionViewLayout = infiniteLayout;     self.collectionView.showsHorizontalScrollIndicator = NO;     self.collectionView.pagingEnabled = YES;      // Register Cells     [self.collectionView registerNib:[UINib nibWithNibName:@"BSCTopNewsHeaderCell" bundle:nil] forCellWithReuseIdentifier:BSCTopNewsCellReuseIdentifier]; } 



Edit3: The crash only seems to occur in special occasions. If the app was in the background, but still in memory and the user opens it up again. It then checks our API for new data, and if it found something will load them and reload the whole outer collectionView. Thats when the crash occurs.

If the CollectionView is reloaded while the app is running without being in the background in the beginning everything is fine.


To make the setup a bit more clear.
like image 597
muffe Avatar asked Aug 12 '13 14:08

muffe


2 Answers

First, drag and drop a UICollectionView to your ViewController in XIB, hook up Delegate, datasource to ViewController(this is only main ViewController)

Don't use 2 different nib cell for 1 CollectionView, because you can only register 1 Nib. Better use DecorationView as HeaderView. Create new class HeaderView : UICollectionReusableView. This UICollectionReusableView is subclass of UIView can reuse inside UICollectionView together with UICollectionViewCell. Now you can register both type:

[self.collectionView registerNib:[UINib nibWithNibName:@"MyCell" bundle:nil] forCellWithReuseIdentifier:@"CELL"]; [self.collectionView registerNib:[UINib nibWithNibName:@"HeaderView" bundle:nil] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"HeaderCell"]; 

Next, you drag another UICollectionView to this HeaderView, hook up with IBOutlet inside HeaderView.h. Here, it's better to set Delegate, DataSource to this class for control. Also register what Nib Cell this CollectionView will use. Do it in awakeFromNib because you registerNib before

- (void)awakeFromNib{     [self.topCollectionView registerNib:[UINib nibWithNibName:@"TopCell" bundle:nil] forCellWithReuseIdentifier:@"TopCell"];      [self.topCollectionView setDataSource:self];     [self.topCollectionView setDelegate:self]; } 

It's will work well, if you store your datasource outside, just make another property and assign to it, then use for return inside datasource here.

If you want to know when click on Cell inside headerView, Use a customDelegate protocol to send delegate to ViewController when click on HeaderCell.

This is my code, hope you understand and can apply your data here:

https://github.com/lequysang/gitfiles02/blob/master/CollectionViewWithDecorationView.zip

like image 74
LE SANG Avatar answered Sep 21 '22 13:09

LE SANG


Looks like your delegate or inner collection view is dead(what does setWeakDelegatePointer: do?). Try Zombies instrument on simulator, it should flag if zombies are the case. Also set "Exceptions breakpoint" for all exceptions in xCode(will aid your debugging when app is run from xCode and not instruments). Also check your -[BSCFrontPageViewController collectionView:cellForItemAtIndexPath:] implementation, it may be releasing that inner collection view on reuse.

Edit: Why not just add header cell as header and not the cell 0(example of header in collection view here)? Also check(just to make sure it's not a reason of crash) your reuse identifiers when you are creationg normal cells for outer collection view. Also send mem warnings periodically when debugging on simulator.

Also, why use another collection view for header? You can use something similar to iCarousel for such layout.

like image 38
Timur Kuchkarov Avatar answered Sep 20 '22 13:09

Timur Kuchkarov