Липкий заголовок UICollectionView исчезает на некоторое время после вставки раздела, когда коллекция прокручивается (эффект отскока)

Я использую UICollectionReusableView в качестве заголовка раздела UICollectionView. Я включил «липкие заголовки» с помощью:

let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
layout?.sectionHeadersPinToVisibleBounds = true

Я вставляю новые разделы в коллекцию с помощью:

collectionView.performBatchUpdates({
    self.collectionView.insertSections(IndexSet(integersIn: collectionView.numberOfSections...viewModel.numberOfSections - 1))
}, completion: nil)

Если вставка происходит при прокрутке коллекции (включен bounce), заголовок на некоторое время исчезнет (см. GIF ниже). Как избежать такого поведения?

Я использую iOS 12.1.4, но такая же проблема возникает и на симуляторах iOS 11.x и 12.x.

Проблема не возникает, если эффект отскока отключен, но я хочу оставить его включенным для более плавного ощущения прокрутки. Я пытался аннулировать макет до/после обновления безрезультатно. Спасибо за советы.

введите здесь описание изображения

EDIT (26/02/2019)
Временное решение: перенос вставки в performWithoutAnimation блокирует исчезновение заголовка решения, но, очевидно, отключает анимацию перезагрузки.

UIView.performWithoutAnimation {
    collectionView.performBatchUpdates({
        self.collectionView.insertSections(IndexSet(integersIn: collectionView.numberOfSections...viewModel.numberOfSections - 1))
    }, completion: nil)
}

person Jakub Marek    schedule 25.02.2019    source источник


Ответы (3)


К сожалению, при вызове PerformBatchUpdates макет автоматически анимирует все элементы. Даже до сих пор нет способа явно указать, какие элементы следует анимировать, а какие нет.

Однако я придумал решение, которое является своего рода анти-шаблоном.

Для вашего класса заголовка переопределите эти методы:

       -(void)setBounds:(CGRect)bounds
        {
            [CATransaction begin];
            [CATransaction setDisableActions:self.shouldDisableAnimations];
            [super setBounds:bounds];
            [CATransaction commit];
        }
    
        //-(void)setCenter:(CGPoint)center
    
    
        - (void)setCenter:(CGPoint)center
        {
            [CATransaction begin];
            [CATransaction setDisableActions:self.shouldDisableAnimations];
            [super setCenter:center];
            [CATransaction commit];
        }

Теперь, если shouldDisableAnimations имеет значение true, автоматические анимации collectionView не будут применяться к нашему заголовку.

Однако отключение анимации для заголовка может вызвать другие сбои (например, когда вы сильно прокручиваете вниз, а затем удаляете все ячейки. заголовок мгновенно переместится наверх, а collectionView прокрутится вверх с анимацией, что приведет к сбою!)

Чтобы справиться с этим, нам нужно установить shouldDisableAnimations для заголовка в правильное время, когда вызывается prepareForCollectionViewUpdates. К сожалению, мы не можем сделать это через атрибут заголовка, так как атрибуты применяются к заголовку после завершения анимации. Поэтому нам нужно сделать прямой доступ к экземпляру заголовка непосредственно из метода prepareForCollectionViewUpdates. (есть несколько способов сделать это. Я сделал это, сохранив слабую ссылку заголовка на свойство класса самого себя)

-(void)prepareForCollectionViewUpdates:(NSArray<UICollectionViewUpdateItem *> *)updateItems
{
    // Keep track of insert and delete index paths
    [super prepareForCollectionViewUpdates:updateItems];
    
    [self.indexPaths2Delete removeAllObjects];
    [self.indexPaths2Insert removeAllObjects];
    
    for (UICollectionViewUpdateItem *update in updateItems)
    {
        if (update.updateAction == UICollectionUpdateActionDelete)
        {
            [self.indexPaths2Delete addObject:update.indexPathBeforeUpdate];
        }
        else if (update.updateAction == UICollectionUpdateActionInsert)
        {
            [self.indexPaths2Insert addObject:update.indexPathAfterUpdate];
        }
    }

    if (self.indexPaths2Insert.count > 0 || self.indexPaths2Delete.count > 0)
    {
        HomeHeaderView.liveInstance.shouldDisableAnimations = false; //since it may cause scrolling, we should enable the animations
    }
    else
        HomeHeaderView.liveInstance.shouldDisableAnimations = true; //there's nothing added or deleted, so keep the header sticked.
}

person Hamidreza Vakilian    schedule 01.10.2019

Приведенное выше решение, которое я предоставил ранее, похоже, не работает на iOS 13.

Во время выполненияBatchUpdates UICollectionView не применяет layoutAttributes для элементов. Единственный способ исправить это — установить фрейм (или любые другие параметры) в заголовке явно из метода prepare() макета. Поскольку выполняется непрерывная анимация, UICollectionView не будет выполнять макет во время анимации.

Я думаю, что Apple должна добавить функцию, чтобы явно отмечать, какие элементы участвуют в анимации, а какие нет. Это можно сделать через layoutAttributes элементов или отдельный метод в UICollectionViewLayout.

Доступ к экземплярам элемента из макета — нет-нет! Но других обходных путей на данный момент нет.

person Hamidreza Vakilian    schedule 22.01.2020

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

person devgariya    schedule 02.03.2020