CollectionView + UIKit Dynamics在performBatchUpdates上崩溃:

时间:2013-12-13 13:41:06

标签: ios objective-c uicollectionview uicollectionviewlayout uikit-dynamics

我遇到了一个奇怪的崩溃并试图整天修复它。 我有一个自定义的UICollectionViewLayout,它基本上会增加细胞的重力和碰撞。

实施效果很好! 当我尝试使用以下命令删除一个单元格时会出现问题:[self.collectionView performBatchUpdates:]。

它给了我以下错误:

2013-12-12 21:15:35.269 APPNAME[97890:70b] *** Assertion failure in -[UICollectionViewData validateLayoutInRect:], /SourceCache/UIKit_Sim/UIKit-2935.58/UICollectionViewData.m:357

2013-12-12 20:55:49.739 APPNAME[97438:70b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView recieved layout attributes for a cell with an index path that does not exist: <NSIndexPath: 0x975d290> {length = 2, path = 0 - 4}'

我的模型处理正确,我可以看到它从中删除该项目!

要删除的项的indexPaths正在对象之间正确传递。 collectionView更新没有崩溃的唯一一次是当我删除它上面的最后一个单元格时,否则就会发生崩溃。

这是我用来删除单元格的代码。

- (void)removeItemAtIndexPath:(NSIndexPath *)itemToRemove completion:(void (^)(void))completion
{
    UICollectionViewLayoutAttributes *attributes = [self.dynamicAnimator layoutAttributesForCellAtIndexPath:itemToRemove];

    [self.gravityBehaviour removeItem:attributes];
    [self.itemBehaviour removeItem:attributes];
    [self.collisionBehaviour removeItem:attributes];

    [self.collectionView performBatchUpdates:^{
        [self.fetchedBeacons removeObjectAtIndex:itemToRemove.row];
        [self.collectionView deleteItemsAtIndexPaths:@[itemToRemove]];
    } completion:nil];   
}

处理单元格属性的CollectionView委托是下面的基本委托。

- (CGSize)collectionViewContentSize
{
    return self.collectionView.bounds.size;
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return [self.dynamicAnimator itemsInRect:rect];
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return [self.dynamicAnimator layoutAttributesForCellAtIndexPath:indexPath];
}

我已经尝试过的事情没有成功: - 使布局无效 - 重新加载数据 - 从UIDynamicAnimator中删除行为并在更新后再次添加

任何见解?

此存储库中提供了有问题的源代码。请检查一下。 Code Repository

最佳。 乔治。

4 个答案:

答案 0 :(得分:12)

经过几周的努力解决这个错误,我们的朋友@ david-h和@erwin的一些有用的见解以及来自Apple的WWDR的一些电话和电子邮件,我能够解决这个问题。只想与社区分享解决方案。

  1. 问题所在。 UIKit Dynamics有一些帮助方法来处理集合视图,这些方法实际上并没有做我认为应该自动执行的内部操作,比如使用 performBatchUpdates执行某些操作时自动更新动态动画单元格执行。所以你必须根据WWDR手动完成,所以我们迭代更新的itens更新他们的NSIndexPaths,这样动态动画师可以给我们正确的单元更新。

  2. 这个错误。即使对单元格插入/删除执行此indexPath更新,我也会在动画中出现大量随机崩溃和奇怪的行为。所以,我按照@erwin给出的提示,在 performBatchUpdates:之后重新实例化 UIDynamicAnimator ,并解决了这种情况下的所有问题。 / p>

  3. 所以代码。

    - (void)removeItemAtIndexPath:(NSIndexPath *)itemToRemove completion:(void (^)(void))completion
    {
        UICollectionViewLayoutAttributes *attributes = [self.dynamicAnimator layoutAttributesForCellAtIndexPath:itemToRemove];
    
        if (attributes) {
            [self.collisionBehaviour removeItem:attributes];
            [self.gravityBehaviour removeItem:attributes];
            [self.itemBehaviour removeItem:attributes];
    
            // Fix the problem explained on 1.
            // Update all the indexPaths of the remaining cells
            NSArray *remainingAttributes = self.collisionBehaviour.items;
            for (UICollectionViewLayoutAttributes *attributes in remainingAttributes) {
                if (attributes.indexPath.row > itemToRemove.row)
                    attributes.indexPath = [NSIndexPath indexPathForRow:(attributes.indexPath.row - 1) inSection:attributes.indexPath.section];
            }
    
            [self.collectionView performBatchUpdates:^{
                completion();
                [self.collectionView deleteItemsAtIndexPaths:@[itemToRemove]];
            } completion:nil];
    
            // Fix the bug explained on 2.
            // Re-instantiate the Dynamic Animator
            self.dynamicAnimator = [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self];
            [self.dynamicAnimator addBehavior:self.collisionBehaviour];
            [self.dynamicAnimator addBehavior:self.gravityBehaviour];
            [self.dynamicAnimator addBehavior:self.itemBehaviour];
        }
    }
    

    我打开了一个雷达解释了这个问题,希望Apple能够在未来的更新中解决这个问题。如果有人复制它,可以在OpenRadar上找到。

    谢谢大家。

答案 1 :(得分:4)

我一直在努力应对类似的情况。

在我的情况下,我使用UIAttachmentBehaviors,因此每个UICollectionViewLayoutAttributes项都有自己的行为。因此,我没有从行为中删除项目,而是从动态动画师中删除了相应的行为。

对我来说,删除中间的UICollectionViewCell似乎有效(没有崩溃),但是如果我尝试删除最后一个单元格,应用程序就会崩溃。

仔细检查动画师的行为(使用调试日志)显示其余项目的索引路径确实在已删除的项目之后超过了一个。手动重置它们本身并不能解决问题。

问题似乎是集合视图中的单元格数量与动态动画制作者-itemsInRect返回的项目数量不匹配:(我的所有单元格始终可见)。

我可以在删除单元格之前删除所有行为来防止崩溃。但当然,当附件消失时,这会导致我的细胞不希望的运动。

我真正需要的是一种重置动态动画制作中的项目而不会完全丢弃它们并重新创建它们的方法。

所以,最后,我想出了一种机制,它基于将行为存储到一边,重新启动动态动画师,并重新添加行为。

它似乎运作良好,可能会进一步优化。

- (void)detachItemAtIndexPath:(NSIndexPath *)indexPath completion:(void (^)(void))completion {

for (UIAttachmentBehavior *behavior in dynamicAnimator.behaviors) {
    UICollectionViewLayoutAttributes *thisItem = [[behavior items] firstObject];
    if (thisItem.indexPath.row == indexPath.row) {
        [dynamicAnimator removeBehavior:behavior];
    }
    if (thisItem.indexPath.row > indexPath.row) {
        thisItem.indexPath = [NSIndexPath indexPathForRow:thisItem.indexPath.row-1 inSection:0];
    }
}

NSArray *tmp = [NSArray arrayWithArray:dynamicAnimator.behaviors];

self.dynamicAnimator = [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self];

for (UIAttachmentBehavior *behavior in tmp) {
    [dynamicAnimator addBehavior:behavior];
}

    // custom algorithm to place cells
for (UIAttachmentBehavior *behavior in dynamicAnimator.behaviors) {
    [self setAnchorPoint:behavior];
}

[self.collectionView performBatchUpdates:^{
    [self.collectionView deleteItemsAtIndexPaths:@[indexPath]];
} completion:^(BOOL finished) {
            completion();
}];

}

答案 2 :(得分:2)

首先,惊人的项目!我喜欢盒子弹跳的方式。似乎总有一个缺失 - 排0.无论如何,任何读过这个对布局感兴趣的人都应该看到它!喜欢动画。

在... Layout.m:

将此方法更改为仅重新加载:

- (void)removeItemAtIndexPath:(NSIndexPath *)itemToRemove completion:(void (^)(void))completion
{
    //assert([NSThread isMainThread]);

    UICollectionViewLayoutAttributes *attributes = [self.dynamicAnimator layoutAttributesForCellAtIndexPath:itemToRemove];
    [self.collisionBehaviour removeItem:attributes];
    [self.gravityBehaviour removeItem:attributes];
    [self.itemBehaviour removeItem:attributes];

    completion();

    [self.collectionView reloadData];
}

我添加了这些日志消息:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSLog(@"ASKED FOR LAYOUT ELEMENTS IN RECT");
    NSArray *foo = [self.dynamicAnimator itemsInRect:rect];
    NSLog(@"...LAYOUT ELEMENTS %@", foo);
    return foo;
}

跑完程序并删除一个中间项目。查看控制台中的索引路径。删除项目时,必须重置索引路径,因为它们现在无法正确反映单元格的新索引。

CollectionViewDynamics[95862:70b] ASKED FOR LAYOUT ELEMENTS IN RECT
CollectionViewDynamics[95862:70b] ...LAYOUT ELEMENTS (
    "<UICollectionViewLayoutAttributes: 0x109405530> index path: (<NSIndexPath: 0xc000000000008016> {length = 2, path = 0 - 1}); frame = (105.41 -102.09; 100.18 100.18); transform = [0.99999838000043739, -0.0017999990280001574, 0.0017999990280001574, 0.99999838000043739, 0, 0]; ",
    "<UICollectionViewLayoutAttributes: 0x10939c2b0> index path: (<NSIndexPath: 0xc000000000018016> {length = 2, path = 0 - 3}); frame = (1 -100.5; 100 100); ",
    "<UICollectionViewLayoutAttributes: 0x10912b200> index path: (<NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}); frame = (3 468; 100 100); "
)
CollectionViewDynamics[95862:70b] *** Assertion failure in -[UICollectionViewData validateLayoutInRect:], /SourceCache/UIKit_Sim/UIKit-2935.80.1/UICollectionViewData.m:357

修复这应该让你去(我希望)。 NSLog多年来一直是我最好的朋友! YMMV

答案 3 :(得分:0)

通过一个字符串

解决了类似的问题
self.<#custom_layout_class_name#>.dynamicAnimator = nil;

每次更新数据源时都必须进行转换

相关问题