今天在项目中遇到一个问题,是UICollectionView的一个DataSource方法- (UICollectionReusableView *)collectionView: viewForSupplementaryElementOfKind: atIndexPath:在同一个VC中被重复调用,第一次调用是正常的,第二次调用就会返回nil,导致crash,而这个方法是不允许返回crash的。
几经定位,最后找到的原因哭笑不得,竟然是忘记删了VC的xib文件。
起因是项目中这个VC之前用了Xib,为了使它更通用,以及以后使用JSPatch打补丁方便,就想把它改成纯代码实现。其中的核心控件是一个自定义的UICollectionView。
我把IBOutlet关联的collectionview属性删除了IBOutlet,这样是删除了关联,并用纯代码创建了对应的collectionView。但是,最大的大意是没有删除VC对应的xib文件。
因为我之前在创建CustomViewController时,是勾选了Also Create XIB file的,那么在用[[CustomViewController alloc]init]创建VC时,系统会自动在mainBundle中寻找同名的xib文件并加载。
这就意味在在加载xib时创建了一个collectionView对象A,同时在我的纯代码中也创建了另一个collectionView对象B,两者的DataSource和Delegate都指向了VC。而我在注册header class时,是用self.collectionView注册的,也就是说只有纯代码创建的collectionView才注册了header class。因为collectionView对象A没有注册header class,那么在使用- (UICollectionReusableView *)collectionView: viewForSupplementaryElementOfKind: atIndexPath:获取对应的UICollectionReusableView对象时,用dequeueReusableSupplementaryViewOfKind: withReuseIdentifier: forIndexPath:方法得到的返回值永远为nil,这就会导致crash。
解决方法就是删除VC对应的xib文件,就这么简单。
相关的错误日志:
*** Assertion failure in -[UICollectionView _createPreparedSupplementaryViewForElementOfKind:atIndexPath:withLayoutAttributes:applyAttributes:], /SourceCache/UIKit/UIKit-2903.2/UICollectionView.m:1401
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the view returned from -collectionView:viewForSupplementaryElementOfKind:atIndexPath
(UICollectionElementKindSectionHeader,<NSIndexPath: 0x145f3f50> {length = 2, path = 0 - 0}) was not retrieved by calling -dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath: or is nil'
(<UICollectionReusableView: 0x145f9400; frame = (0 0; 320 20); layer = <CALayer: 0x145f90c0>>)
*** Assertion failure in -[DFCollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.30.14/UICollectionView.m:3690
Exception: could not dequeue a view of kind: UICollectionElementKindSectionHeader with identifier CollectionEmptyDataHeader - must register a nib or a class for the identifier or connect a prototype cell in a storyboard
*** Assertion failure in -[DFCollectionView _createPreparedSupplementaryViewForElementOfKind:atIndexPath:withLayoutAttributes:applyAttributes:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.30.14/UICollectionView.m:1583
libc++abi.dylib: terminate_handler unexpectedly threw an exception
if(kind == UICollectionElementKindSectionHeader)
{
@try {
theView = [theCollectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header" forIndexPath:theIndexPath];
}
@catch (NSException * e) {
NSLog(@"Exception: %@", e);
theView = [[UICollectionReusableView alloc] init];
}
@finally {
return theView;
}
}
这份代码虽然没起作用,不建议使用,但是调试时可以用其中的NSLog打印的信息来辅助定位问题。来源:uicollectionview - viewForSupplementaryElementOfKind is crashing on "index 0 beyond bounds for empty array" - Stack Overflow
这里有一个类似的问题,但问题原因完全不一样:
iOS UICollectionView 的坑:不能重复调用 dequeueReusableSupplementaryViewOfKind - 简书