UICollectionView sticky header disappears for while after inserting section when collection is overscrolled (bounce effect)

柔情痞子 提交于 2020-03-22 08:01:33

问题


I'm using UICollectionReusableView as a header of UICollectionView section. I enabled "sticky headers" with:

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

I'm inserting new sections to collection with:

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

If the insertion happens when collection is overscrolled (bounce is enabled), header will disappear for while (see GIF below). How can I avoid this behaviour?

I am using iOS 12.1.4 but same issue happens also on iOS 11.x and 12.x simulators.

The problem doesn't happen if bounce effect is turned off but I want to keep it on for smoother scroll feeling. I tried invalidate layout before/after update with no result. Thanks for advices.

EDIT (02/26/2019)
Workaround: Wrapping the insertion to performWithoutAnimation block solve header disappearing but obviously disable reload animation.

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

回答1:


Unfortunately, by calling performBatchUpdates, the layout automatically animates all items itself. Even until now, there's no way to explicitly tell which items to animate and which not to.

However, I came up with a resolution which is kind of an anti-pattern.

For your header class, override these methods:

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

Now if shouldDisableAnimations is true, the collectionView's automatic animations won't be applied to our header.

However, disabling the animations for the header may cause other glitches (e.g. when you scroll down a lot, and then remove all the cells. the header will jump to top instantly and the collectionView will scroll to top with animation resulting in a glitch!)

To handle this we need to set shouldDisableAnimations for the header in the correct time which is when prepareForCollectionViewUpdates gets called. Unfortunately, we cannot do this through the header's attribute since the attributes are applied to the header after animation gets finished. So we need to make direct access to the header's instance directly from within prepareForCollectionViewUpdates method. (there are several ways to do this. I did this by keeping a weak reference of the header on a class property of itself)

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



回答2:


The above solution I provided earlier seems not to work on iOS 13.

During performBatchUpdates, UICollectionView doesn't apply layoutAttributes for elements. The only way to fix this is to make set the frame (or any other parameters) on the header explicitly from within prepare() method of the layout. Because there's an ongoing animation being performed, UICollectionView won't layout during the animation.

I think Apple has to add a feature to explicitly mark which elements participate in the animation and which not. This can be done through the layoutAttributes of elements or a separate method in UICollectionViewLayout.

Accessing the element instances from layout is a no-no! But there's no other workarounds at the moment.




回答3:


I was also having the same problem, but the work around I have done is reload the collectionView(the sticky header didn't disappears) it self instead of insert.



来源:https://stackoverflow.com/questions/54867434/uicollectionview-sticky-header-disappears-for-while-after-inserting-section-when

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!