7

我被一个奇怪的崩溃困住了,整天都在试图修复它。我有一个自定义 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 中删除行为并在更新后再次添加它们

有什么见解吗?

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

最好的。乔治。

4

4 回答 4

12

在与这个错误斗争了几个星期之后,我们的朋友 @david-h 和 @erwin 提供了一些有用的见解,以及来自 Apple WWDR 的一些电话和电子邮件,我能够解决这个问题。只想与社区分享解决方案。

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

  2. 错误。即使在单元格插入/删除时执行此 indexPath 更新,我也有很多随机崩溃和动画的奇怪行为。因此,我遵循了@erwin 给出的提示,该提示包括在performBatchUpdates:之后重新实例化UIDynamicAnimator :并解决了这种情况下的所有问题。

所以代码。

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

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

谢谢大家。

于 2014-01-02T17:56:56.787 回答
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();
}];

}

于 2013-12-17T08:17:08.210 回答
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

于 2013-12-13T21:32:11.547 回答
0

用一串解决了类似的问题

self.<#custom_layout_class_name#>.dynamicAnimator = nil;

每次更新数据源时都必须强制转换

于 2014-03-31T10:58:45.697 回答