Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionView crashes when rearranging items between sections

I have trouble animating changes between sections in a UICollectionView, my program keeps crashing, what is wrong with it?

I have a collection view which has four sections:

0: A
1: B
2: C
3: D

I want to transform it to have only three sections with the same items:

0: A
1: B, C
2: D

And I want to animate this transformation:

// Initial state

NSArray *source = @[ @[@"A"], @[@"B"], @[@"C"], @[@"D"] ];


// Some data source methods

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return [source[section] count];
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return [source count];
}


// Transformation: that works but I have to keep an empty section

source = @[ @[@"A"], @[@"B", @"C"], @[@"D"], @[] ];

[collection performBatchUpdates:^{
    [collection moveItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]
                        toIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
    [collection moveItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]
                        toIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]];
    [collection moveItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:2]
                        toIndexPath:[NSIndexPath indexPathForItem:1 inSection:1]];
    [collection moveItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:3]
                        toIndexPath:[NSIndexPath indexPathForItem:0 inSection:2]];
} completion:nil];


// Transformation: that crashes!

source = @[ @[@"A"], @[@"B", @"C"], @[@"D"] ];

[collection performBatchUpdates:^{
    [collection moveItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]
                        toIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
    [collection moveItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]
                        toIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]];
    [collection moveItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:2]
                        toIndexPath:[NSIndexPath indexPathForItem:1 inSection:1]];
    [collection moveItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:3]
                        toIndexPath:[NSIndexPath indexPathForItem:0 inSection:2]];
    [collection deleteSections:[NSIndexSet indexSetWithIndex:3]];
} completion:nil];

I keep getting crashes, either an internal assertion failure: Assertion failure in -[UICollectionView _endItemAnimations]..., or sometimes, even more weird, a malloc error: incorrect checksum for freed object....

If I don't call deleteSections: it doesn't work either. If I put first, it doesn't change anything. If I remove the moveItemAtIndexPath:toIndexPath: which have the same source and destination, it doesn't change anything. If I don't do it in a batch block, it obviously crashes at the first command. Did I overlook something?

like image 777
Guillaume Avatar asked Nov 12 '22 04:11

Guillaume


1 Answers

An extract from this very interesting article about UICollectionView:

When inserting a new section within the batch update block, one must not insert any items into that section – that will be handled implicitly. In fact, inserting items into a newly-inserted section in a batch update block creates “ghost” layers which get stuck in the collection view layer hierarchy. Presumably this is a just bug with UICollectionView, as this behavior is not documented and no exception is thrown

Knowing this, I find it not surprising at all that deleting of section and moveItemFromIndexPath:toIndexPath do not work well together either. I guess it's "kind of the same bug".

The solution I used:

I put a fake invisible cell in the section I should have deleted. That permitted me to keep the same behavior as if I had done a moveItemFromIndexPath:toIndexPath:. Of course, I adapted my datasource accordingly!

like image 143
Aurelien Porte Avatar answered Nov 15 '22 07:11

Aurelien Porte