web-dev-qa-db-fra.com

UICollectionView et vue supplémentaire (en-tête)

Essayer d'ajouter une vue supplémentaire dans mon UICollectionView en tant qu'en-tête. J'ai des problèmes pour le faire fonctionner.

J'utilise un UICollectionViewFlowLayout personnalisé pour renvoyer un contentSize qui est toujours au moins 1 pixel plus grand que le cadre (j'utilise un UIFreshControl qui ne fonctionnera que si le UICollectionView défile et définit collectionView.contentSize ne fait rien) et à invalidateLayout sur sectionInsert et itemSize changements:

-(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);
}

J'ai créé une classe UICollectionReusableView qui est juste un UIView avec un 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;
}

Essayer de l'implémenter:

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;

J'inscris la classe:

[self.collectionView registerClass:[CollectionHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"CollectionHeaderView"];

et implémentez le délégué:

-(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;
}

La ligne

collectionViewFlowLayout.headerReferenceSize = CGSizeMake(0, 100);

provoque l'erreur:

*** 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'

Si je le commente, il s'exécute mais pas d'en-tête.

Que fais-je de mal ou que je ne mets pas en œuvre?

17
Padin215

J'ai rencontré le même problème, mais mon application s'est bloquée après avoir fait apparaître par programmation mon UICollectionViewController. Dans une raison quelconque (je pense que c'est juste un bogue dans le SDK) self.collectionView était en vie après la destruction de son contrôleur, provoquant ainsi cet échec:

*** 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'

La solution consiste simplement à remplacer -dealloc dans UICollectionViewController et à libérer self.collectionView manuellement. Code ARC:

- (void)dealloc {

    self.collectionView = nil;
}

J'espère que cela fera gagner du temps à quelqu'un.

13
m8labs

Les réponses dans cette rubrique sont assez anciennes et ne fonctionnent pas sous iOS8 et iOS9. Si vous rencontrez toujours le problème décrit, consultez la rubrique suivante: crash UICollectionView et vue supplémentaire

[~ # ~] éditez [~ # ~] :

Voici la réponse, au cas où le lien deviendrait invalide:

Si vous utilisez une disposition personnalisée dans votre vue de collection, vérifiez si sa source de données est nulle dans:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

Le résultat devrait ressembler à ceci:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
    if (self.collectionView.dataSource != nil) {
        // Your layout code    
        return array;
    }
    return nil;
}

Cette modification résout le problème suivant:

  • Échec de l'assertion dans - [UICollectionView _createPreparedSupplementaryViewForElementOfKind: atIndexPath: withLayoutAttributes: applyAttributes:]

Vous pouvez également vérifier le sujet suivant, mais cela n'a rien résolu dans mon cas:

Suppression de l'espace vide, si l'en-tête de section est masqué dans UICollectionView

J'espère que cela vous aidera et que vous ne perdrez pas autant de temps que moi.

10
Michael

J'ai nettoyé mon code et supprimé l'UICollectionView que j'avais créé dans IB et j'ai tout créé en code. J'ai relancé le it et j'ai eu une erreur différente et j'ai réalisé que je n'avais pas défini le délégué ou dataSource dans IB. J'ai fait:

self.collectionView.delegate = self;
self.collectionView.datasource = self;

Mais cela ne devait pas être suffisant.

Donc, avec UICollectionView créé dans IB et ne définissant pas la delgate/source de données dans IB, mais plutôt dans le 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"];

était un problème. Je l'ai refaite pour créer le UICollectionView all dans le 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;

et j'obtiens une erreur différente:

*** 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'

que je vais essayer de comprendre.

ÉDITER:

compris, j'enregistrais mal l'en-tête:

[collectionView registerClass:[CollectionHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"CollectionHeaderView"];

changé pour:

UINib *headerNib = [UINib nibWithNibName:@"CollectionHeaderView" bundle:nil];

[collectionView registerNib:headerNib forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"CollectionHeaderView"];

et tout fonctionne. Je ne sais pas exactement comment le registerClass: ne fonctionnait pas cependant.

7
Padin215

Cette réponse est similaire aux autres où vous créez une méthode qui vérifie la source de données, mais c'est un peu plus simple. Il semble que le problème soit lié à la disposition de la vue de la collection qui reste après la publication de la collection. J'ai donc mis à zéro les propriétés de la mise en page comme ci-dessous et l'erreur a disparu.

deinit {
    let layout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout
    layout.estimatedItemSize = CGSizeZero
    layout.sectionInset = UIEdgeInsetsZero
    layout.headerReferenceSize = CGSizeZero
}
1
Chris Garrett

J'ai également eu cette erreur ennuyeuse:

*** 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'

et j'ai trouvé des gens qui résolvaient cette erreur en faisant

- (void)dealloc {
    self.collectionView = nil;
}

ou en Swift:

deinit {
    collectionView = nil
}

mais ni l'un ni l'autre n'ont résolu mon crash. J'ai essayé quelques trucs et le "hack" suivant a résolu ce bug ennuyeux:

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    if collectionView.dataSource != nil {
        return someHeaderSize
    } else {
        return CGSizeZero
    }
}
1
Simon

Si vous regardez le message d'erreur, il indique que la source de données n'est pas définie. Cela devrait le corriger:

self.collectionView.dataSource = self;

Assurez-vous que votre contrôleur de vue implémente le protocole de source de données:

YourViewController : UIViewController<UICollectionViewDataSource>

1
Fran Sevillano

Un bogue iOS9, dans le délégué de la couche setloc de viewcontroller nil.

- (void)dealloc{

    if ([[[UIDevice currentDevice] systemVersion] hasPrefix:@"9"]) {
        self.collectionView.layer.delegate = nil;
    }
}
0
liuyuning

J'avais le même problème et de même, je n'enregistrais pas correctement l'en-tête. il y a plusieurs threads ici qui suggèrent tous d'utiliser un Nib pour définir l'en-tête, mais je l'ai fait différemment et fonctionne bien.

J'ai un en-tête personnalisé SizeCollectionHeader, qui hérite de l'UICollectionReusableView. Il est enregistré comme ceci.

    [self.collectionView registerClass:[GRSizeCollectionHeader class] forSupplementaryViewOfKind:@"UICollectionElementKindSectionHeader"
                                                                             withReuseIdentifier:@"SupplementaryViewIdentifier"];

et fonctionne bien.

mon problème initial était qu'il avait une valeur "Kind" aléatoire comme celle-ci.

    [self.collectionView registerClass:[GRSizeCollectionHeader class] forSupplementaryViewOfKind:@"SupplementaryViewKind"
                                                                             withReuseIdentifier:@"SupplementaryViewIdentifier"];

Cela va planter.

Je voulais donc simplement dire à quiconque cherche ici que vous n'avez pas besoin d'utiliser une plume.

0
BROK3N S0UL

J'ai toujours eu la même erreur. J'avais configuré le CustomHeaderView pour ma collectionView. J'avais une classe de Collection Reusable View sur mon CustomHeaderView et j'avais défini l'identifiant. J'ai une demi-heure à essayer de comprendre pourquoi cela échouait.

Arrêt de l'application en raison d'une exception non interceptée 'NSInternalInconsistencyException', raison: 'n'a pas pu retirer une vue de type: UICollectionElementKindSectionFooter avec l'identifiant SectionHeader - doit enregistrer une pointe ou une classe pour l'identifiant ou connecter une cellule prototype dans un storyboard'

Vous devez lire attentivement les journaux ... leçon apprise.

'n'a pas pu retirer une vue de type: UICollectionElementKindSectionFooter

La raison en est que dans Storyboard - dans la collectionView Identity Inspector, j'avais vérifié les deux accessoires: Section Header et Section Footer. Je n'avais besoin que d'un en-tête de section. Décochez simplement le pied de page ... build and run..BOOM!

0
Ronaldoh1

Essaye ça :

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

Ça a marché pour moi;)

0
user3369214