UICollectionView粘性标头在集合过分滚动时插入节后消失了一段时间(反弹效果)

时间:2019-02-25 13:34:19

标签: ios swift uicollectionview uicollectionreusableview uicollectionviewflowlayout

我使用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)

如果插入是在集合过度滚动(启用反弹)时发生的,则标头会消失一会儿(请参见下面的GIF)。 如何避免这种行为?

我正在使用iOS 12.1.4,但在iOS 11.x和12.x模拟器上也会发生相同的问题。

如果关闭了弹跳效果,则不会出现此问题,但是我想保持该状态以使滚动感觉更流畅。我尝试在更新之前/之后使布局无效,但没有结果。感谢您的建议。

enter image description here

编辑(02/26/2019)
解决方法:将插入内容包装到performWithoutAnimation块中可以解决标题消失的问题,但显然会禁用重新加载动画。

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

3 个答案:

答案 0 :(得分:1)

不幸的是,通过调用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将随着动画滚动到顶部,从而导致小故障!)

要处理此问题,我们需要在调用prepareForCollectionViewUpdates的正确时间内为标头设置shouldDisableAnimations。不幸的是,我们无法通过标题的属性来执行此操作,因为在动画完成后,属性会应用于标题。因此,我们需要直接从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.
}

答案 1 :(得分:0)

我之前提供的上述解决方案似乎无法在iOS 13上运行。

在performBatchUpdates期间,UICollectionView不会为元素应用layoutAttributes。解决此问题的唯一方法是在版面的prepare()方法内显式设置标头上的框架(或其他参数)。由于正在执行动画,因此UICollectionView不会在动画期间进行布局。

我认为Apple必须添加一项功能,以明确标记哪些元素参与动画,哪些不参与。这可以通过元素的layoutAttributes或UICollectionViewLayout中的单独方法来完成。

从布局访问元素实例是不行的!但是目前没有其他解决方法。

答案 2 :(得分:0)

我也有同样的问题,但是我要做的工作是重新加载而不是插入的collectionView(粘性标头没有消失)。