UICollectionView and Supplementary View (header)

2019-03-12 11:35发布

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?

10条回答
可以哭但决不认输i
2楼-- · 2019-03-12 12:14

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.

查看更多
Explosion°爆炸
3楼-- · 2019-03-12 12:16

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!

查看更多
萌系小妹纸
4楼-- · 2019-03-12 12:20

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楼-- · 2019-03-12 12:20

Try this :

self.collectionView?.dataSource = nil
self.collectionView = nil

It worked for me ;)

查看更多
Ridiculous、
6楼-- · 2019-03-12 12:22

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.

查看更多
Bombasti
7楼-- · 2019-03-12 12:25

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.

查看更多
登录 后发表回答