7

私は奇妙なクラッシュに悩まされており、一日中それを修正しようとしています. 基本的にセルに重力と衝突の振る舞いを追加するカスタム UICollectionViewLayout があります。

実装はうまくいきます![self.collectionView performBatchUpdates:] を使用して 1 つのセルを削除しようとすると、問題が発生します。

次のエラーが表示されます。

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 には Collection Views を操作するためのヘルパー メソッドがいくつかありますが、これらのメソッドは、 performBatchUpdates:を使用したアクションが実行されたときに動的にアニメーション化されたセルを自動的に更新するなど、自動的に行う必要があると思われる内部処理を実際には実行しません。したがって、WWDR に従って手動で行う必要があるため、動的アニメーターがセルを適切に更新できるように、NSIndexPath を更新する更新された itens を反復処理します。

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

Appleが将来のアップデートでこれを修正することを期待して、問題を説明するレーダーを開きました。誰かがそれを複製したい場合は、OpenRadarで入手できます。

みんなありがとう。

于 2014-01-02T17:56:56.787 に答える
4

私は似たような状況で苦労してきました。

私の場合、UIAttachmentBehaviors を使用しているため、各 UICollectionViewLayoutAttributes アイテムは独自の動作を取得します。そのため、動作からアイテムを削除する代わりに、動的アニメーターから適切な動作を削除しています。

私にとっては、中間の UICollectionViewCell の削除は機能しているように見えますが (クラッシュはありません)、最後のセルを削除しようとするとアプリがクラッシュします。

アニメーターの動作を (デバッグ ログを使用して) 綿密に調査すると、残りのアイテムのインデックス パスが、削除されたアイテムの 1 つ後ろに実際にずれていることがわかります。手動でリセットしても、問題は解決しません。

問題は、コレクション ビュー内のセルの数と、ダイナミック アニメーターの -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 が常に 1 つ欠けているようです。とにかく、これを読んでいてレイアウトに興味がある人は必見です。アニメーションが大好きです。

...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

同様の問題を1つの文字列で解決しました

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

データソースを更新するたびにキャストする必要があります

于 2014-03-31T10:58:45.697 に答える