CollectionView + UIKit Dynamics 在 performBatchUpdates 上崩溃:

Posted

技术标签:

【中文标题】CollectionView + UIKit Dynamics 在 performBatchUpdates 上崩溃:【英文标题】:CollectionView + UIKit Dynamics crashing on performBatchUpdates: 【发布时间】:2013-12-13 13:41:06 【问题描述】:

我遇到了一个奇怪的崩溃,整天都在尝试修复它。 我有一个自定义 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

最好的。 乔治。

【问题讨论】:

尝试在返回单元格的数据委托方法中添加一些代码并在控制台上监控请求。同样,将日志记录添加到返回部分数、单元数的方法中。也许 cv 甚至在开始批量更新之前就请求后者(在这种情况下,您需要将您的信标删除移出块之前。真的,我很同情 - 批量更新对我来说工作得很好,我做了一个小更改我的单元格(添加视图),然后wham,它开始崩溃,我不得不使每一个更改都使用重新加载:-( 谢谢大卫。这是加载、删除和崩溃序列的输出。看来计数还可以。 Link to output log 能否在performBatchUpdates之前和之后,以及在每个代码行之前,中间和之后的块内添加日志。也为了让我高兴在删除后添加日志 self.fetchedBeacons 中的项目数(使中间日志消息),然后更新日志。我知道这是一个 PITA,但我对 CV 所做的是编写一个非常简单的单视图应用程序,它在单元格中使用标签,以使用各种做事模式。如果这有效并且复杂的代码不会搞砸它的简历(很可能)。 CV 似乎改变了细胞复杂程度的行为,这真的让我们很难! 我在“-[UICollectionViewData validateLayoutInRect:] 中的断言失败”上进行了搜索,并获得了大量点击。一种可能的解决方案:***.com/a/19378624/1633251 - 另一个很好的解决方案 ***.com/a/18411860/1633251 。嗯,您是否正在验证该项目是否可见?如果不是,也许只是重新加载数据就可以了…… @DavidH 谢谢大卫。我已经尝试过建议的链接。重新加载数据并使布局无效似乎没用。似乎 dynamicAnimator 完全控制了它。此外,我使用的是自定义布局,因此这里永远不会调用标题大小。我在一个工作示例中隔离了代码。如果您有时间并想深入研究此错误,请检查一下。 Code Repository最好!!! 【参考方案1】:

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

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

    错误。即使在单元格插入/删除时执行此 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];
    

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

谢谢大家。

【讨论】:

您是否测试过这在 ios 7.1 中是否仍然存在问题?【参考方案2】:

我一直在为类似的情况而苦苦挣扎。

在我的例子中,我使用的是 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();
];

【讨论】:

【参考方案3】:

首先,令人惊叹的项目!我喜欢盒子弹跳的方式。似乎总是缺少一个 - 第 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

【讨论】:

嗨@david-h!非常感谢。很高兴你喜欢这部动画。最后的样子更有趣。让我们保持联系,以便我可以在准备就绪时向您展示。不过,这里没有成功。更新索引路径是重新加载 CV 应该处理的事情,并且手动执行会引入许多其他奇怪的错误。由于我有点厌倦了填写雷达/错误报告并且从未得到反馈,我使用了我的一个帐户支持请求。让我们看看苹果工程师能否解决这个问题。我会很高兴发布最终答案(如果有的话)。谢谢,保重!【参考方案4】:

一串解决类似问题

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

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

【讨论】:

这是我第一次尝试解决这个问题,它产生了另一个与集合视图相关的崩溃。

以上是关于CollectionView + UIKit Dynamics 在 performBatchUpdates 上崩溃:的主要内容,如果未能解决你的问题,请参考以下文章

UICollectionView 不加载-iOS

转换到横向后如何更新 UITableViewCell 内的 UICollectionView

xcode 开发 创建uicollectionview 崩掉了

iOS UICollectionView之-(水平滚动)

iOS UICollectionView之三(基本用法)

ios . -- UICollectionView --cell 自适应