【问题标题】:Weird animation behaviour with UICollectionView (insert & delete item) [closed]UICollectionView(插入和删除项目)的奇怪动画行为[关闭]
【发布时间】:2013-01-24 16:31:07
【问题描述】:

从 UICollectionView 插入或删除项目时,动画期间似乎出现了一个额外的单元格,并且这个额外的单元格向错误的方向移动。我用 UITableView 也试过了,没问题。

问题视频在这里https://dl.dropbox.com/u/11523469/CollectionViewBug.mov,左侧是集合视图,右侧是表格视图。每个单元格中的数字是该单元格创建时的indexPath.item值。

该问题首先在 0:08 到 0:12(插入)之间的视频中出现,然后在 0:16 到 0:20(删除)再次出现。

项目可在此处获得https://dl.dropbox.com/u/11523469/CollectionViewBug.zip

即插入单元格时,插入单元格下方的所有单元格都会向下移动,以便为新单元格腾出空间。但是这个额外的单元格出现并与其他单元格重叠并向上移动。

同样,当删除一个单元格时,被删除单元格下方的所有单元格都会向上移动,以填补该单元格原来所在的位置。但是这个额外的单元格出现并与其他单元格重叠并向下移动。

在集合视图上执行的第一个操作(插入或删除)不会导致此问题。但在所有后续操作中,问题都存在。

有没有其他人在使用 UICollectionView 时遇到过同样的问题?有没有人有解决方案或解决方法?

谢谢!

【问题讨论】:

    标签: iphone ios uitableview ios6 uicollectionview


    【解决方案1】:

    我想出了一个解决方法,它似乎可以解决问题,但对所提供的示例非常具体。我的猜测是,当单元格被重用时,它们的起点错误,这会导致奇怪的动画。

    我将 Storyboard 更改为使用 UICollectionViewFlowLayout 的子类:

    // MyFlowLayout - subclass of UICollectionViewFlowLayout
    
    #import "MyFlowLayout.h"
    
    @interface MyFlowLayout ()
    
    @property (strong) NSMutableArray *deleteIndexPaths;
    @property (strong) NSMutableArray *insertIndexPaths;
    @property (assign) float rowOffset;
    
    @end
    
    @implementation MyFlowLayout
    
    -(id)initWithCoder:(NSCoder *)aDecoder
    {
        if (self = [super initWithCoder:aDecoder])
        {
            // minimumInteritemSpacing may be adjusted upwards but this example ignores that
            self.rowOffset = self.itemSize.height + self.minimumInteritemSpacing;
        }
    
        return self;
    }
    
    // As per Mark Pospesel corrections to CircleLayout
    
    - (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
    {
        // Keep track of insert and delete index paths
        [super prepareForCollectionViewUpdates:updateItems];
    
        self.deleteIndexPaths = [NSMutableArray array];
        self.insertIndexPaths = [NSMutableArray array];
    
        for (UICollectionViewUpdateItem *update in updateItems)
        {
            if (update.updateAction == UICollectionUpdateActionDelete)
            {
                [self.deleteIndexPaths addObject:update.indexPathBeforeUpdate];
            }
            else if (update.updateAction == UICollectionUpdateActionInsert)
            {
                [self.insertIndexPaths addObject:update.indexPathAfterUpdate];
            }
        }
    }
    
    - (void)finalizeCollectionViewUpdates
    {
        [super finalizeCollectionViewUpdates];
        // release the insert and delete index paths
        self.deleteIndexPaths = nil;
        self.insertIndexPaths = nil;
    }
    
    // The next two methods have misleading names as they get called for all visible cells on     both insert and delete
    
    - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
    {
        // Must call super
        UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
        if (!attributes)
            attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
    
        if ([self.insertIndexPaths containsObject:itemIndexPath]) {
            // Initial position for an inserted cell is it's final position - fades in
            CGRect frame = attributes.frame;
            frame.origin.y = itemIndexPath.row * self.rowOffset;
            attributes.frame = frame;
            attributes.zIndex = -1; // stop the inserted cell bleeding through too early in the animation
        }
        if ([self.deleteIndexPaths count]) {
            NSIndexPath *deletedPath = self.deleteIndexPaths[0];  // Might be more than one but this example ignores that
            if (itemIndexPath.row > deletedPath.row) {
                // Anything after the deleted cell needs to slide up from the position below it's final position
                // Anything before the deleted cell doesn't need adjusting
                CGRect frame = attributes.frame;
                frame.origin.y = ((itemIndexPath.row + 1) * self.rowOffset);
                attributes.frame = frame;
            }
        }
    
        return attributes;
    }
    
    - (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
    {
        UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];
        if (!attributes)
            attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
    
        // I would have expected the final positions to already be correct but my guess is that re-used cells
        // are not considered until after the animation block settings have been generated
        CGRect frame = attributes.frame;
        frame.origin.y = itemIndexPath.row * self.rowOffset;
        attributes.frame = frame;
    
        if ([self.deleteIndexPaths containsObject:itemIndexPath]) {
            // Fade out the deleted cell
            attributes.alpha = 0.0;
        }
    
        return attributes;
    }
    
    @end
    

    【讨论】:

    • 谢谢你,它有效(对我的实际项目进行了一些调整)。你同意这是 iOS 中的一个错误吗?这肯定不是预期的行为吗?
    • 是的,我称之为错误。我相当确定只有重复使用的单元格才会出现意外的动画行为。
    • 感谢您发布此信息。非常有用。
    • 不知道为什么,但这并没有解决我的问题。插入后的单元格首先呈现在第一个插入位置,然后移动到正确的位置。它看起来很丑……我找不到任何解决方案……苹果为什么不解决这个问题没有道理……感谢您的任何建议。马丁。
    【解决方案2】:

    如果有人来这里寻找 MonoTouch 的答案,这就是我通过翻译 Gareth's answer 得到的。

    一个基类,定义EnableAnimationFix 和两个虚方法:ApplyAnimationFixToAppearingItemApplyAnimationFixToDisappearingItem

    public class CollectionViewFlowLayout : UICollectionViewFlowLayout
    {   
        protected List<int> _insertedItems = new List<int> ();
        protected List<int> _deletedItems = new List<int> ();
    
        protected virtual bool EnableAnimationFix {
            get { return false; }
        }
    
        protected virtual void ApplyAnimationFixToAppearingItem (int index, UICollectionViewLayoutAttributes attrs)
        {
            throw new NotImplementedException ();
        }
    
        protected virtual void ApplyAnimationFixToDisappearingItem (int index, UICollectionViewLayoutAttributes attrs)
        {
            throw new NotImplementedException ();
        }
    
        public override UICollectionViewLayoutAttributes InitialLayoutAttributesForAppearingItem (NSIndexPath path)
        {
            var attrs = base.InitialLayoutAttributesForAppearingItem (path);
            if (!EnableAnimationFix) {
                return attrs;
            }
    
            attrs = attrs ?? LayoutAttributesForItem (path);
            if (attrs != null)
                ApplyAnimationFixToAppearingItem (path.Row, attrs);
    
            return attrs;
        }
    
        public override UICollectionViewLayoutAttributes FinalLayoutAttributesForDisappearingItem (NSIndexPath path)
        {
            var attrs = base.FinalLayoutAttributesForDisappearingItem (path);
            if (!EnableAnimationFix) {
                return attrs;
            }
    
            if (attrs == null && _deletedItems.Contains (path.Row)) {
                // Calling LayoutAttributesForItem will cause an exception so we return now.
                // I think this happens when last and only item is deleted, and there are no other cells in cell pool.
                return null;
            }
    
            attrs = attrs ?? LayoutAttributesForItem (path);
            if (attrs != null)
                ApplyAnimationFixToDisappearingItem (path.Row, attrs);
    
            return attrs;
        }
    
        public override void PrepareForCollectionViewUpdates (UICollectionViewUpdateItem [] updateItems)
        {
            base.PrepareForCollectionViewUpdates (updateItems);
    
            if (!EnableAnimationFix)
                return;
    
            _insertedItems.Clear ();
            _deletedItems.Clear ();
    
            foreach (var update in updateItems) {
                if (update.UpdateAction == UICollectionUpdateAction.Insert) {
                    _insertedItems.Add (update.IndexPathAfterUpdate.Row);
                } else if (update.UpdateAction == UICollectionUpdateAction.Delete) {
                    _deletedItems.Add (update.IndexPathBeforeUpdate.Row);
                }
            }
        }
    
        public override void FinalizeCollectionViewUpdates ()
        {
            base.FinalizeCollectionViewUpdates ();
    
            if (!EnableAnimationFix)
                return;
    
            _insertedItems.Clear ();
            _deletedItems.Clear ();
        }
    }
    

    这是我实际的集合视图布局代码:

    public class DraftsLayout : CollectionViewFlowLayout
    {
    
        // ... 
    
    
        protected override bool EnableAnimationFix {
            get { return true; }
        }
    
        protected override void ApplyAnimationFixToAppearingItem (int index, UICollectionViewLayoutAttributes attrs)
        {
            if (_insertedItems.Contains (index)) {
                SetXByIndex (attrs, index);
                attrs.ZIndex = -1;
            }
    
            int deletedToTheLeft = _deletedItems.Count (i => i < index);
            if (deletedToTheLeft > 0) {
                SetXByIndex (attrs, index + deletedToTheLeft);
            }
        }
    
        protected override void ApplyAnimationFixToDisappearingItem (int index, UICollectionViewLayoutAttributes attrs)
        {
            SetXByIndex (attrs, index);
    
            if (_deletedItems.Contains (index)) {
                attrs.Alpha = 0;
            }
        }
    
        const int SnapStep = 150;
        static void SetXByIndex (UICollectionViewLayoutAttributes attrs, int index)
        {
            var frame = attrs.Frame;
            frame.X = index * SnapStep;
            attrs.Frame = frame;
        }
    }
    

    请注意,此代码应该可以很好地处理一批中的多个删除。
    向加雷斯致敬。

    【讨论】:

      【解决方案3】:

      我一直在使用 reloadData 或 reloadItemsAtIndexPaths 遇到各种奇怪的动画行为。当我不使用这些方法时,动画行为似乎像宣传的那样工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-07-29
        • 2020-01-19
        • 1970-01-01
        • 1970-01-01
        • 2011-04-14
        • 1970-01-01
        • 2020-11-09
        相关资源
        最近更新 更多