UICollectionView и дополнительный просмотр (заголовок)

Попытка добавить дополнительный вид в мой UICollectionView как заголовок. У меня проблемы с работой.

Я использую пользовательский UICollectionViewFlowLayout для возврата contentSize, который всегда на 1 пиксель больше, чем кадр (я использую UIFreshControl, который будет работать, только если свитки UICollectionView и установка collectionView.contentSize прямо ничего не делает) и invalidateLayout на sectionInsert и itemSize изменяется:

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

Я создал класс UICollectionReusableView, который является просто UIView с 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;
}

Попытка реализовать его:

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;

Я регистрирую класс:

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

и реализовать делегат:

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

Линия

collectionViewFlowLayout.headerReferenceSize = CGSizeMake(0, 100);

вызывает ошибку:

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

Если я прокомментирую это, он запускается, но нет заголовка.

Что я делаю неправильно или не реализую?

Ответ 1

Я столкнулся с аналогичной проблемой, но мое приложение потерпело крах после того, как я программно вывел свой UICollectionViewController. По какой-то причине (я думаю, что это просто ошибка в SDK) self.collectionView был жив после того, как его "контроллер уничтожил", тем самым вызывая этот сбой:

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

Решение просто переопределяет -dealloc в UICollectionViewController и отдает self.collectionView вручную. Код ARC:

- (void)dealloc {

    self.collectionView = nil;
}

Надеюсь, это сэкономит время для кого-то.

Ответ 2

Ответы в этом разделе довольно старые и не работают в iOS8 и iOS9. Если у вас все еще есть описанная проблема, ознакомьтесь со следующей темой: Ошибка UICollectionView и дополнительного просмотра

ИЗМЕНИТЬ

Вот ответ, если ссылка становится недействительной:

Если вы используете пользовательский макет в своем представлении коллекции, проверьте, нет ли его источника данных:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

Результат должен выглядеть примерно так:

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

Это изменение устраняет следующую проблему:

  • Ошибка утверждения в - [UICollectionView _createPreparedSupplementaryViewForElementOfKind: atIndexPath: withLayoutAttributes: applyAttributes:]

Вы также можете проверить следующий раздел, однако в моем случае он ничего не разрешил:

Удаление пустого пространства, если заголовок раздела скрыт в UICollectionView

Надеюсь, это поможет вам, и вы не потратите столько времени, сколько я сделал.

Ответ 3

Я очистил свой код и удалил UICollectionView, который я создал в IB, и создал его в коде. Я снова запустил его и получил другую ошибку, и понял, что я не установил делегат или источник данных в IB. Я сделал:

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

Но это не должно было быть достаточно хорошим.

Итак, с UICollectionView, созданным в IB, и не устанавливающим delgate/datasource в IB, а скорее в коде:

[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"];

была проблема. Я переделал его, чтобы создать UICollectionView все в коде:

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;

и я получаю другую ошибку:

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

который я попытаюсь выяснить.

EDIT:

понял это, я неправильно зарегистрировал заголовок:

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

переключился на:

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

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

и все работает. Не совсем уверен, как registerClass: не работал.

Ответ 4

Если вы посмотрите на сообщение об ошибке, в нем говорится, что источник данных не установлен. Это должно исправить это:

self.collectionView.dataSource = self;

Убедитесь, что ваш контроллер просмотра реализует протокол источника данных:

YourViewController : UIViewController<UICollectionViewDataSource>

Ответ 5

Этот ответ аналогичен другим, где вы создаете метод, который проверяет источник данных, но он немного проще. Похоже, что проблема связана с планированием вида коллекций, который появляется после публикации коллекции. Поэтому я отключил свойства макета, как показано ниже, и ошибка исчезла.

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

Ответ 6

Я также получил эту досадную ошибку:

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

и я обнаружил, что некоторые люди решают эту ошибку, выполняя

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

или быстро:

deinit {
    collectionView = nil
}

однако ни один из них не разрешил мой крах. Я попробовал несколько вещей, и следующий "взлом" решил эту неприятную ошибку:

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

Ответ 7

У меня была такая же проблема, и аналогичным образом я неправильно регистрировал заголовок. здесь есть несколько потоков, которые предлагают использовать Nib для определения заголовка, но я сделал это по-другому и отлично работает.

У меня есть собственный заголовок SizeCollectionHeader, который наследует UICollectionReusableView. Он зарегистрирован следующим образом.

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

и работает нормально.

Моя первоначальная проблема заключалась в том, что было случайное значение "Вид", подобное этому.

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

Это приведет к сбою.

Поэтому я просто хотел прокомментировать кого-нибудь, кто здесь смотрит, что вам не нужно использовать нить.

Ответ 8

Попробуйте следующее:

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

Это сработало для меня;)

Ответ 9

Я продолжал получать ту же ошибку. Я настроил CustomHeaderView для моего коллекцииView. У меня был класс Collection Reusable View для моего CustomHeaderView, и я установил идентификатор. Я полчаса пытался выяснить, почему это было неудачно.

Завершение приложения из-за неперехваченного исключения "NSInternalInconsistencyException", причина: "не может удалить вид вида: UICollectionElementKindSectionFooter с идентификатором SectionHeader - должен зарегистрировать nib или класс для идентификатора или подключить прототип ячейки в раскадровке

Вам нужно внимательно прочитать журналы... извлеченный урок.

'не может удалить вид  вида: UICollectionElementKindSectionFooter

Причина в том, что в раскадровке - в инспекторе Identity CollectionView я проверил оба варианта: заголовок раздела и нижний колонтитул секции. Мне нужен только заголовок раздела. Просто снимите нижний колонтитул... создайте и запустите.

Ответ 10

Ошибка iOS9, в viewcontroller dealloc установить слой делегат nil.

- (void)dealloc{

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