UICollectionView move items to new section

2019-09-19 10:20发布

问题:

I'm having serious troubles with the animation of a UICollectionView, similar to the problem mentioned here: UICollectionView crashes when rearranging items between sections

Let's say I have a UICollectionView with 5 different sections, each with 10 cells. I would like to update the CollectionView with an animation, so the cells will be reordered into 10 sections. No cells with new content will be added nor existing cells removed.

Therefore I'm performing a batch update:

 [_resultCollectionView performBatchUpdates:^{

        [_resultCollectionView insertSections:insertSections];
        [_resultCollectionView deleteSections:deleteSections];

        //[_resultCollectionView insertItemsAtIndexPaths:insertIndexPaths];
        //[_resultCollectionView deleteItemsAtIndexPaths:deleteIndexPaths];

        for (all items which need to be moved...) {
            [_resultCollectionView moveItemAtIndexPath:sourcePath toIndexPath:destinationPath];
        }

    } completion:^(BOOL finished) {
        //[_resultCollectionView reloadData];
        nil;
    }];

If I perform insertSections, deleteSections, insertItemsAtIndexPath, deleteItemsAtIndexPath and reloadData once the block was performed everything works fine which means my dataSource and delegates work properly in theory. Except it's not animated yet ...

If I perform insertSections, deleteSections and moveItemAtIndexPath (which should work since the cells only get rearranged) i get this little bug: cannot move a row into a newly inserted section (5)

If I perform insertSections, deleteSections and moveItemAtIndexPath but exclude any movement into a newly inserted section I obviously get invalid number of items in the sections.

Does anybody have a solution for this problem?

回答1:

I followed john1034's link and figured out a quite tricky way to do it. You have to split up the work in three steps:

  1. Adding new sections if necessary. The new sections will be empty first.
  2. Adding, removing and moving cells within the new and existing sections.
  3. Deleting sections if necessary.

For sure there are many lines which can be further improved. Especially the search functions are quite inefficient...

static BOOL addingSections = NO;
static BOOL updatingCollectionView = NO;
static BOOL removingSections = NO;

- (void)reloadResultCollectionView

    //IndexUnloadedArray: a link to my CollectionViewData before the update
    //IndexArray: a link to my CollectionViewData after the update

    //start only if something changed
    if (![IndexArray isEqual:IndexUnloadedArray]) {
        NSMutableIndexSet *deleteSections = [[NSMutableIndexSet alloc] init];
        NSMutableIndexSet *insertSections = [[NSMutableIndexSet alloc] init];
        NSMutableArray *deleteIndexPaths = [[NSMutableArray alloc] init];
        NSMutableArray *insertIndexPaths = [[NSMutableArray alloc] init];

        //step 1 - add collectionView sections

        for (int i = 0; i < IndexArray.count; i++) {
            if (i >= IndexUnloadedArray.count) {
                [insertSections addIndex:i];
            }
        }
        NSLog(@"insert sections:%@", insertSections);

        _sectionAmount = (int)_distanceUnloadedArray.count + (int)insertSections.count;

        addingSections = YES;
        [_resultCollectionView performBatchUpdates:^{
            [_resultCollectionView insertSections:insertSections];
        } completion:^(BOOL finished) {
            nil;
        }];
        addingSections = NO;

        //step 2 - update collectionView

        //adding cells if there are not enough
        for (int i = 0; i < IndexArray.count; i++) {
            for (int j = 0; j < (int)[[IndexArray objectAtIndex:i] count]; j++) {
                NSNumber *searchIndex = [[IndexArray objectAtIndex:i] objectAtIndex:j];
                bool found = NO;

                for (int k = 0; k < IndexUnloadedArray.count; k++) {
                    if ([[IndexUnloadedArray objectAtIndex:k] containsObject:searchIndex]) {
                        found = YES;
                        k = (int)IndexUnloadedArray.count;
                    }
                }

                if (!found) {
                    [insertIndexPaths addObject:[NSIndexPath indexPathForRow:j inSection:i]];
                }
            }
        }
        NSLog(@"insert cells:%@", insertIndexPaths);

        //deleting cells if there are too many
        for (int i = 0; i < IndexUnloadedArray.count; i++) {
            if (![deleteSections containsIndex:i]) {
                for (int j = 0; j < (int)[[IndexUnloadedArray objectAtIndex:i] count]; j++) {
                    NSNumber *searchIndex = [[IndexUnloadedArray objectAtIndex:i] objectAtIndex:j];
                    bool found = NO;

                    for (int k = 0; k < IndexArray.count; k++) {
                        if ([[IndexArray objectAtIndex:k] containsObject:searchIndex]) {
                            found = YES;
                            k = (int)IndexArray.count;
                        }
                    }

                    if (!found) {
                        [deleteIndexPaths addObject:[NSIndexPath indexPathForRow:j inSection:i]];
                    }
                }
            }
        }
        NSLog(@"deleting cells:%@", deleteIndexPaths);

        updatingCollectionView = YES;
        [_resultCollectionView performBatchUpdates:^{
            [_resultCollectionView insertItemsAtIndexPaths:insertIndexPaths];
            [_resultCollectionView deleteItemsAtIndexPaths:deleteIndexPaths];

            for (int i = 0; i < IndexUnloadedArray.count; i++) {
                for (int j = 0; j < [[IndexUnloadedArray objectAtIndex:i] count]; j++) {
                    NSIndexPath *sourcePath = [NSIndexPath indexPathForRow:(j) inSection:i];
                    NSNumber *searchIndex = [[IndexUnloadedArray objectAtIndex:i] objectAtIndex:j];
                    NSIndexPath *destinationPath;

                    for (int k = 0; k < IndexArray.count; k++) {
                        if ([[IndexArray objectAtIndex:k] containsObject:searchIndex]) {
                            NSInteger *row = [[IndexArray objectAtIndex:k] indexOfObject:searchIndex];
                            destinationPath = [NSIndexPath indexPathForItem:row inSection:k];

                            if (sourcePath != destinationPath) {
                                NSLog(@"moving cell from %ld.%ld to %ld.%ld (%@)", (long)sourcePath.section, (long)sourcePath.row, (long)destinationPath.section, (long)destinationPath.row);
                                [_resultCollectionView moveItemAtIndexPath:sourcePath toIndexPath:destinationPath];
                            } else {
                                NSLog(@"object %ld.%ld stays in position", (long)sourcePath.section, (long)sourcePath.row);
                            }
                        }
                    }
                }
            }

        } completion:^(BOOL finished) {
            nil;
        }];
        updatingCollectionView = NO;

        //step 3 - deleting sections if there are too many
        for (int i = 0; i < IndexUnloadedArray.count; i++) {
            if (i >= IndexArray.count) {
                [deleteSections addIndex:i];
            }
        }
        NSLog(@"delete sections:%@", deleteSections);

        _sectionAmount = (int)_distanceArray.count;

        removingSections = YES;
        [_resultCollectionView performBatchUpdates:^{
            [_resultCollectionView deleteSections:deleteSections];
        } completion:^(BOOL finished) {
            //update table header and footer
            [_resultCollectionView reloadData];
        }];
        removingSections = NO;

    }
}


- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView
{
    return _sectionAmount;
}


- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section
{
    if (addingSections) {
        if (section < _distanceUnloadedArray.count) {
            return [[_distanceUnloadedArray objectAtIndex:section] count];
        } else {
            return 0;
        }
    }

    if (updatingCollectionView) {
        if (section < _distanceArray.count) {
            return [[_distanceArray objectAtIndex:section] count];
        } else {
            return 0;
        }
    }

    if (removingSections) {
        return [[_distanceArray objectAtIndex:section] count];
    }


    return [[_distanceArray objectAtIndex:section] count];
}