8

I am making a collection view with all basic stuff, like default FlowLayout, unique section.
Assuming I'm correctly using collectionView:CellForItemAtIndexPath: and all the other dataSource protocols

I have mainData, which is an array of dictionnaries (the original data), mainDataReferenceOrdered, mainDataNameOrdered and mainDataQuantiteOrdered are other arrays containing the same data as mainData (same elements pointed).
dataToDisplay is the controller's current array pointer to ordered data.

When reordering, I just change data in collection view batch, like this:

[itemCollectionControl.collectionView performBatchUpdates:^{
        dataToDisplay = mainDataReferenceOrdered; //or any other ordered array
        [itemCollectionControl.collectionView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, itemCollectionControl.collectionView.numberOfSections)]];
    } completion:^(BOOL finished) {}];

But the collection fades all cells, even if they are already visible, or at the right place.

I read the Apple documentation on UICollectionView, but I don't know what I missed. I also read other threads about that, but still looking for how I must do.

What do the batch looks to know which animation to apply on cells ?


My solution

This is the final code I use, certainly the closest of iOS programming guidelines I guess.

[itemCollectionControl.collectionView performBatchUpdates:^{
    NSArray *oldOrder = dataToDisplay;
    dataToDisplay = mainDataNBContainersOrdered;
    for (NSInteger i = 0; i < oldOrder.count; i++) {
        NSIndexPath *fromIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
        NSInteger j = [dataToDisplay indexOfObject:oldOrder[i]];
        NSIndexPath *toIndexPath = [NSIndexPath indexPathForRow:j inSection:0];
        [itemCollectionControl.collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
    }
} completion:^(BOOL finished) {
    if (finished) {
        [itemCollectionControl.collectionView reloadData];
    }
}];

I simply browse all old ordered items, check their new position, and apply it with -[UICollectionView moveItemAtIndexPath:toIndexPath:]
There's still some duplicated cells glitches, but it looks fine on iOS7 (not on iOS6). The completion might be useless on iOS7, I force the right order at the end on the iOS6 shuffling.

Edit

I think I found a solution, but I'm no longer able to test on that project. Maybe adding just 2 lines of code should fix this awful glitch.

before any call of -[UICollectionView moveItemAtIndexPath:toIndexPath:], call -[UICollectionView beginUpdates], and finally after all moves -[UICollectionView endUpdates].

If anyone able to test it found that it works, please tell me.

Community
  • 1
  • 1
Crazyrems
  • 2,551
  • 22
  • 40

1 Answers1

6

The collection view doesn't know the identity of the items in your data model, so it has no way of knowing that they've moved. So you've got to explicitly tell the collection view where each cell moves to inside the batch update using moveItemAtIndexPath:toIndexPath:. You can calculate this yourself by looping over items in the from array and looking up their positions in the to array. Something like this (typed from memory, so sorry about any typos):

[itemCollectionControl.collectionView performBatchUpdates:^{
    for (NSInteger i = 0; i < mainDataReferenceOrdered.count; i++) {
        NSIndexPath *fromIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
        NSInteger j = [mainDataNameOrdered indexOfObject:mainDataReferenceOrdered[i]];
        NSIndexPath *toIndexPath = [NSIndexPath indexPathForRow:j inSection:0];
        [itemCollectionControl.collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
    }
} completion:^(BOOL finished) {}];

If you've got a lot (multiple thousands) of items, you might want to consider using sets for faster lookups.

A more generally applicable approach is to use something like TLIndexPathTools that can calculate the batch updates for you. Take a look at the Shuffle sample project.

Timothy Moose
  • 9,895
  • 3
  • 33
  • 44
  • Use this make my animation [look funky](http://stackoverflow.com/questions/13698275/uicollectionview-moveitematindexpathtoindexpath-issues-moving-items-not-on-scr), the first looks great, but badly recycle cells (it uses already shown values). I changed the data source in the completion handler, but it looks creepy, some cells duplicate, some merge, and they pop different values at the beginning and at the end of the animation. When must I set the new data, because when set before animation it seems to mangle reused cells, and after too... there's much more moving cells that I have in ma data. – Crazyrems Oct 23 '13 at 10:04
  • You configure the cell's data in `cellForRowAtIndexPath`. If you're seeing bad data, make sure `cellForRowAtIndexPath` works with reused cells. If you're using a custom cell class, you can re-initialize the cell's state in `prepareForReuse`. – Timothy Moose Oct 23 '13 at 14:40
  • It seems that when you call `moveItemAtIndexPath:toIndexPath:`, every cell from `AtIndexPath` to `toIndexPath` shift, and so for each move, even if we are batching. That's why there's more moving cells than there is in the section. I tried this on iOS 7 and its looks fine, this appears to glitch only with iOS 6. Thanks. – Crazyrems Oct 24 '13 at 08:31
  • And `prepareForReuse` does not fix my problem, because data was not my problem :), I'm pretty sure that's the way I handle moves. – Crazyrems Oct 24 '13 at 08:49
  • Oh iOS6. I'm guessing you're using `UICollectionViewFlowLayout`? That layout is unusable in iOS6 for batch updates with more than 1 screen of cells. We ended up writing our own `VCollectionViewGridLayout` class. There is a sample project called "Sort & Filter" in our [`VCollectionViewGridLayout` library] (https://github.com/vast-eng/uicollectionview-gridlayout) where you can toggle between our layout and flow layout to see the improvement. "Sort & Filter" works fine with flow layout in iOS7 though. – Timothy Moose Oct 24 '13 at 09:41
  • Yeah I mean iOS 6.1 ;), but yes my project is targeted to iOS 6.1. And I'm using the `UICollectionViewController` with the default flow layout, I can't spend too much time on creating my own flowLayout. – Crazyrems Oct 24 '13 at 15:09