how UICollectionView horizontal scrolling, deleting last item, animation not working

 

Questions


I have a UICollectionView. It scrolls horizontally, has only a single row of items, and behaves like a paging UIScrollView. I’m making something along the lines of the Safari tab picker, so you can still see the edge of each item. I only have one section.

If I delete an item that is not the last item, everything works as expected and a new item slides in from the right.

If I delete the last item, then the collection view’s scroll position jumps to the N-1th item (doesn’t smoothly animate), and then I see the Nth item (the one I deleted) fade out.

This behaviour isn’t related to the custom layout I made, as it occurs even if I switch it to use a plain flow layout. I’m deleting the items using:

[self.tabCollectionView deleteItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:index inSection:0]]];

Has anyone else experienced this? Is it a bug in UICollectionView, and is there a workaround?

 

 

————————————————-

Answer

I managed to get my implementation working using the standard UICollectionViewFlowLayout. I had to create the animations manually.

First, I caused the deleted cell to fade out using a basic animation:

- (void)tappedCloseButtonOnCell:(ScreenCell *)cell {

    // We don't want to close our last screen.
    if ([self screenCount] == 1u) {
        return;
    }

    [UIView animateWithDuration:UINavigationControllerHideShowBarDuration
                     animations:^{
                         // Fade out the cell.
                         cell.alpha = 0.0f;
                     }
                     completion:^(BOOL finished) {

                         NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
                         UIViewController *screen = [self viewControllerAtIndex:indexPath.item];

                         [self removeScreen:screen animated:YES];
                     }];
}

Next, I caused the collection view to scroll to the previous cell. Once I’ve scrolled to the desired cell, I remove the deleted cell.

- (void)removeScreen:(UIViewController *)screen animated:(BOOL)animated {

    NSParameterAssert(screen);

    NSInteger index = [[self.viewControllerDictionaries valueForKeyPath:kViewControllerKey] indexOfObject:screen];

    if (index == NSNotFound) {
        return;
    }

    [screen willMoveToParentViewController:nil];

    if (animated) {

        dispatch_time_t popTime = DISPATCH_TIME_NOW;
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index
                                                 inSection:0];

        // Disables user interaction to make sure the user can't interact with
        // the collection view during the time between when the scroll animation
        // ends and the deleted cell is removed.
        [self.collectionView setUserInteractionEnabled:NO];

        // Scrolls to the previous item, if one exists. If we are at the first
        // item, we just let the next screen slide in from the right.
        if (index > 0) {
            popTime = dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC);
            NSIndexPath *targetIndexPath = [NSIndexPath indexPathForItem:index - 1
                                                  inSection:0];
            [self.collectionView scrollToItemAtIndexPath:targetIndexPath
                                        atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally
                                                animated:YES];
        }

        // Uses dispatch_after since -scrollToItemAtIndexPath:atScrollPosition:animated:
        // doesn't have a completion block.
        dispatch_after(popTime, dispatch_get_main_queue(), ^{

            [self.collectionView performBatchUpdates:^{
                [self.viewControllerDictionaries removeObjectAtIndex:index];
                [self.collectionView deleteItemsAtIndexPaths:@[indexPath]];
                [screen removeFromParentViewController];
                [self.collectionView setUserInteractionEnabled:YES];
            } completion:NULL];
        });

    } else {
        [self.viewControllerDictionaries removeObjectAtIndex:index];
        [self.collectionView reloadData];
        [screen removeFromParentViewController];
    }

    self.addPageButton.enabled = YES;
    [self postScreenChangeNotification];
}

The only part that is slightly questionable is the dispatch_after(). Unfortunately, -scrollToItemAtIndexPath:atScrollPosition:animated: does not have a completion block, so I had to simulate it. To avoid timing problems, I disabled user interaction. This prevents the user from interacting with the collection view before the cell is removed.

Another thing I had to watch for is I have to reset my cell’s alpha back to 1 due to cell reuse.

I hope this helps you with your Safari-style tab picker. I know your implementation is different from mine, and I hope that my solution works for you too.

cocoa-touch,ios,uicollectionview,uikit

Facebook Comments

Website Comments

  1. $6 domino's pizza
    Reply

    Excellent post. I used to be checking continuously this blog and I am impressed!
    Extremely helpful information particularly the last phase :
    ) I handle such information much. I was looking for this certain info for a very long time.
    Thank you and good luck.

Post a comment