可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Trying to add a a Supplementary view into my UICollectionView
as a header. I'm having issues getting it to work.
I use a custom UICollectionViewFlowLayout
to return a contentSize
that is always at least 1 pixel larger then the frame (I am using a UIFreshControl
which will only work if the UICollectionView
scrolls, and setting collectionView.contentSize
directly does nothing) and to invalidateLayout
on sectionInsert
and itemSize
changes:
-(void)setSectionInset:(UIEdgeInsets)sectionInset {
if (UIEdgeInsetsEqualToEdgeInsets(super.sectionInset, sectionInset)) {
return;
}
super.sectionInset = sectionInset;
[self invalidateLayout];
}
-(void) setItemSize:(CGSize)itemSize {
if (CGSizeEqualToSize(super.itemSize, itemSize)) {
return;
}
super.itemSize = itemSize;
[self invalidateLayout];
}
- (CGSize)collectionViewContentSize
{
CGFloat height = [super collectionViewContentSize].height;
// Always returns a contentSize larger then frame so it can scroll and UIRefreshControl will work
if (height < self.collectionView.bounds.size.height) {
height = self.collectionView.bounds.size.height + 1;
}
return CGSizeMake([super collectionViewContentSize].width, height);
}
I created a UICollectionReusableView
class which is just a UIView
with a UILabel
:
@implementation CollectionHeaderView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSArray *arrayOfViews = [[NSBundle mainBundle] loadNibNamed:@"CollectionHeaderView" owner:self options:nil];
if ([arrayOfViews count] < 1) {
return nil;
}
if (![[arrayOfViews objectAtIndex:0] isKindOfClass:[UICollectionViewCell class]]) {
return nil;
}
self = [arrayOfViews objectAtIndex:0];
self.headerLabel.text = @"This is a header. There are many like it.";
self.backgroundColor = [UIColor yellowColor];
}
return self;
}
Trying to implement it:
DatasetLayout *collectionViewFlowLayout = [[DatasetLayout alloc] init];
collectionViewFlowLayout.itemSize = CGSizeMake(360, 160);
collectionViewFlowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
collectionViewFlowLayout.sectionInset = UIEdgeInsetsMake(16, 16, 16, 16);
collectionViewFlowLayout.minimumInteritemSpacing = 16;
collectionViewFlowLayout.minimumLineSpacing = 16;
collectionViewFlowLayout.headerReferenceSize = CGSizeMake(0, 100);
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:collectionViewFlowLayout];
collectionView.translatesAutoresizingMaskIntoConstraints = FALSE;
collectionView.backgroundColor = [UIColor yellowColor];
collectionView.delegate = self;
collectionView.dataSource = self;
I register the class:
[self.collectionView registerClass:[CollectionHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"CollectionHeaderView"];
and implement the delegate:
-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
CollectionHeaderView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"CollectionHeaderView" forIndexPath:indexPath];
headerView.headerLabel.text = @"Blarg!";
return headerView;
}
The line
collectionViewFlowLayout.headerReferenceSize = CGSizeMake(0, 100);
causes the error:
*** Assertion failure in -[UICollectionView _createPreparedSupplementaryViewForElementOfKind:atIndexPath:withLayoutAttributes:], /SourceCache/UIKit_Sim/UIKit-2380.17/UICollectionView.m:1150
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView dataSource is not set'
If I comment it out, it runs but no header.
What am I doing wrong or not implementing?
回答1:
I faced with the similar problem, but my app crashed after I programmatically pop my UICollectionViewController. In some reason (I think it's just a bug in SDK) self.collectionView was alive after its' controller destroy, thus causing this failure:
*** Assertion failure in -[UICollectionView _createPreparedSupplementaryViewForElementOfKind:atIndexPath:withLayoutAttributes:applyAttributes:], /SourceCache/UIKit/UIKit-2935.137/UICollectionView.m:1305
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView dataSource is not set'
The solution is just override -dealloc in UICollectionViewController and release self.collectionView manually. ARC code:
- (void)dealloc {
self.collectionView = nil;
}
Hope this will save time for somebody.
回答2:
The answers in this topic are quite old, and do not work in iOS8 and iOS9. If you are still having the described issue, check out the following topic: UICollectionView and Supplementary View crash
EDIT:
Here is the answer, in case the link becomes invalid:
If you are using custom layout in your collection view, check if its datasource is nil in:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
The result should look something like this:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
if (self.collectionView.dataSource != nil) {
// Your layout code
return array;
}
return nil;
}
This change resolves the following issue:
- Assertion failure in -[UICollectionView _createPreparedSupplementaryViewForElementOfKind:atIndexPath:withLayoutAttributes:applyAttributes:]
You can also check the following topic, however it didn't resolve anything in my case:
Removing empty space, if the section header is hidden in the UICollectionView
Hope it will help you, and you won't waste as much time as I did.
回答3:
I cleaned up my code and removed the UICollectionView I had created in IB and created it all in code. I ran the it again and got a different error and realized I didn't set the delegate or dataSource in IB. I did:
self.collectionView.delegate = self;
self.collectionView.datasource = self;
But that must not have been good enough.
So with UICollectionView created in IB and not setting the delgate/datasource in IB, but rather in the code:
[self.view bringSubviewToFront:self.collectionView];
self.collectionView.collectionViewLayout = collectionViewFlowLayout;
self.collectionView.backgroundColor = [UIColor orangeColor];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.collectionView registerClass:[DatasetCell class] forCellWithReuseIdentifier:@"cvCell"];
[self.collectionView registerClass:[CollectionHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"CollectionHeaderView"];
was an issue. I redid it to create the UICollectionView all in code:
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:collectionViewFlowLayout];
collectionView.translatesAutoresizingMaskIntoConstraints = FALSE;
collectionView.backgroundColor = [UIColor yellowColor];
collectionView.delegate = self;
collectionView.dataSource = self;
[collectionView registerClass:[DatasetCell class] forCellWithReuseIdentifier:@"cvCell"];
[collectionView registerClass:[CollectionHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"CollectionHeaderView"];
[self.view addSubview:collectionView];
self.collectionView = collectionView;
and i get a different error:
*** Assertion failure in -[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:], /SourceCache/UIKit_Sim/UIKit-2380.17/UICollectionView.m:2249
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'could not dequeue a view of kind: UICollectionElementKindSectionHeader with identifier CollectionHeaderView - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
which I'll try to figure out.
EDIT:
figured it out, I was registering the header incorrectly:
[collectionView registerClass:[CollectionHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"CollectionHeaderView"];
switched to:
UINib *headerNib = [UINib nibWithNibName:@"CollectionHeaderView" bundle:nil];
[collectionView registerNib:headerNib forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"CollectionHeaderView"];
and everything works. Not entirely sure how the registerClass:
wasn't working though.
回答4:
If you look at the error message, it says that the data source is not set. This should fix it:
self.collectionView.dataSource = self;
Make sure your view controller implements the data source protocol:
YourViewController : UIViewController<UICollectionViewDataSource>
回答5:
This answer is similar to the others where you create a method that checks for the data source, but it's a bit simpler. It seems the issue is related to the collection view's layout sticking around after the collection has been released. So I zeroed out the layout's properties as below and the error disappeared.
deinit {
let layout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.estimatedItemSize = CGSizeZero
layout.sectionInset = UIEdgeInsetsZero
layout.headerReferenceSize = CGSizeZero
}
回答6:
I also got this annoying error:
*** Assertion failure in -[UICollectionView _createPreparedSupplementaryViewForElementOfKind:atIndexPath:withLayoutAttributes:applyAttributes:], /SourceCache/UIKit/UIKit-3347.44/UICollectionView.m:1400
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView dataSource is not set'
and I found some people solving this error by doing
- (void)dealloc {
self.collectionView = nil;
}
or in swift:
deinit {
collectionView = nil
}
however neither of these solved my crash.
I tried a few things out and the following "hack" solved this annoying bug:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if collectionView.dataSource != nil {
return someHeaderSize
} else {
return CGSizeZero
}
}
回答7:
I was having the same issue and similarly, I was not registering the header correctly. there are several threads on here that all suggest using a Nib to define the header but I have done it differently and is working fine.
I have a custom header SizeCollectionHeader, which inherits the UICollectionReusableView. It is registered like this.
[self.collectionView registerClass:[GRSizeCollectionHeader class] forSupplementaryViewOfKind:@"UICollectionElementKindSectionHeader"
withReuseIdentifier:@"SupplementaryViewIdentifier"];
and is working fine.
my initial problem was that had a random "Kind" value like this.
[self.collectionView registerClass:[GRSizeCollectionHeader class] forSupplementaryViewOfKind:@"SupplementaryViewKind"
withReuseIdentifier:@"SupplementaryViewIdentifier"];
This will crash.
So I just wanted to comment for anybody that is looking here that you don't need to use a nib.
回答8:
Try this :
self.collectionView?.dataSource = nil
self.collectionView = nil
It worked for me ;)
回答9:
I kept getting the same error. I had set up the CustomHeaderView for my collectionView. I had class of the Collection Reusable View to my CustomHeaderView and i had set the identifier. I 1/2 an hour trying to figure out why this was failing.
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'could not dequeue a view
of kind: UICollectionElementKindSectionFooter with identifier
SectionHeader - must register a nib or a class for the identifier or
connect a prototype cell in a storyboard'
You need to read the logs carefully...lesson learned.
'could not dequeue a view
of kind: UICollectionElementKindSectionFooter
The reason is because In storyboard - in the collectionView Identity Inspector I had checked both Accessories: Section Header and Section Footer. I only needed section header. Simply uncheck footer...build and run..BOOM!
回答10:
A iOS9 bug, In viewcontroller's dealloc set layer delegate nil.
- (void)dealloc{
if ([[[UIDevice currentDevice] systemVersion] hasPrefix:@"9"]) {
self.collectionView.layer.delegate = nil;
}
}