问题
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.
回答1:
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
回答2:
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.
回答3:
Can you try the following, scroll the main uicollectionview so the header is not showing... (further better) then on simulator try to perform a memory warning... using Hardware->Simulate memory warning...
This should probably create a cell recycling and deletion (the uicollectionview should destroy anything that is not important right now... if this happens, the header will become deallocated and you still hold a weak reference to it... so anything that you do will happens because of bad access... also this is "imposible" to reproduce on the simulator due to lack of memory warning (only the ones that you create in a explicit way)..
The view looks heavy so can you try doing this and post the results?
回答4:
Pulling our "Answer" out of the comments.
Sadly none of the answers helped. I ended up refactoring the whole thing and then the problem disappeared. I even had an pretty detailed talk with an apple DTS engineer who also didn't knew what to do. So we just refactored. Sorry :/
来源:https://stackoverflow.com/questions/18189311/bad-access-on-uicollectionview-setcollectionviewlayoutanimated