13

我有一个 UICollectionView。它水平滚动,只有一行项目,并且表现得像一个分页 UIScrollView。我正在按照 Safari 选项卡选择器的方式制作一些东西,因此您仍然可以看到每个项目的边缘。我只有一节。

如果我删除一个不是最后一个项目的项目,一切都会按预期工作,并且一个新项目会从右侧滑入。

如果我删除最后一个项目,则集合视图的滚动位置会跳转到第 N-1 个项目(动画不流畅),然后我看到第 N 个项目(我删除的那个)淡出。

此行为与我制作的自定义布局无关,因为即使我将其切换为使用普通流布局也会发生这种情况。我正在使用以下方法删除项目:

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

有没有其他人经历过这个?它是 UICollectionView 中的错误,是否有解决方法?

4

6 回答 6

4

我设法让我的实现使用标准工作UICollectionViewFlowLayout。我必须手动创建动画。

首先,我使用基本动画使已删除的单元格淡出:

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

接下来,我使集合视图滚动到上一个单元格。滚动到所需的单元格后,我将删除已删除的单元格。

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

唯一有点可疑的部分是dispatch_after(). 不幸-scrollToItemAtIndexPath:atScrollPosition:animated:的是,没有完成块,所以我不得不模拟它。为了避免时间问题,我禁用了用户交互。这可以防止用户在删除单元格之前与集合视图进行交互。

我必须注意的另一件事是,由于单元重复使用,我必须将单元的 alpha 重置回 1。

我希望这可以帮助您使用 Safari 风格的标签选择器。我知道您的实现与我的不同,我希望我的解决方案也适用于您。

于 2012-11-15T18:24:35.080 回答
1

我知道这已经有了答案,但我以稍微不同的方式实现了这一点,不需要在设定的时间间隔后进行调度。

在您的删除方法中,您将进行检查以确定是否正在删除最后一项。如果是这样调用:

if(self.selection == self.assets.count-1 && self.selection != 0){
    isDeleting = YES;
    [collection scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:self.selection-1 inSection:0] atScrollPosition:UICollectionViewScrollPositionLeft animated:YES];
}

假设 selection 是您要删除的选定项目。这将滚动到其左侧的项目。请注意 if 语句检查这不是唯一的项目。如果是这样,呼叫将崩溃,因为没有 -1 行。

然后您可以实现以下方法,该方法在滚动动画完成时调用。我只是在 deleteObjectInCollection 方法中将 isDeleting 设置为 no,这一切似乎都有效。

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{
    if(isDeleting){
        [self deleteObjectInCollection];
    }
}

我希望这有帮助。

于 2013-10-22T19:20:17.597 回答
1

这将起作用:

collectionView.collectionViewLayout.invalidateLayout()

collectionView.layoutIfNeeded()
于 2018-01-11T13:52:04.777 回答
0

这是另一种解决方案(targetIndexPath 是要删除的单元格的 indexPath)。这段代码可以简单地放在 removeCellAtIndexPath:(NSIndexPath*)targetIndexPath 方法中,你就完成了(假设我从我的代码到公共代码的改编是正确的,否则问我,我会尽力提供帮助)。

// (Here it's assumed that self.collectionView.collectionViewLayout has been assigned a UICollectionViewFlowLayout before; note the word "FLOW" in that class name) 
UICollectionViewFlowLayout* layout = (UICollectionViewFlowLayout*) self.collectionView.collectionViewLayout;

// Find index path of the last cell in collection view:
NSIndexPath* lastIndexPath = [self lastItemIndexPath];

// Extend content area temporarily to fill out space left by the last row after deletion, if last row is visible:
BOOL lastRowWasVisible = NO;
if ([self.collectionView icn_cellAtIndexPathIsVisible:lastIndexPath]) {

    lastRowWasVisible = YES;

    // Adapt section spacing to temporarily fill out the space potentially left after removing last cell:
    CGFloat cellWithLineSpacingHeight = 79.0f + 8.0f; // Height of my cell + one line spacing
    layout.sectionInset = UIEdgeInsetsMake(0.0f, 0.0f, cellWithLineSpacingHeight, 0.0f);
}

// Remove the cell:
[self.collectionView performBatchUpdates:^{

    [self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:targetIndexPath]];

} completion:^(BOOL finished) {

    // Only scroll if we had the last row visible:
    if (lastRowWasVisible) {

        NSIndexPath* lastItemIndexPath = [self lastItemIndexPath];

        // Run a custom scroll animation for two reasons; 1. that way we can reset the insets when animation is finished, and 2. using the "animated:YES" option lags here for some reason:
        [UIView animateWithDuration:0.3f animations:^{
            [self.collectionView scrollToItemAtIndexPath:prevItemIndexPath atScrollPosition:UICollectionViewScrollPositionBottom animated:NO];
        } completion:^(BOOL finished) {

            // Reset the space placeholder once having scrolled away from it:
            layout.sectionInset = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f);
        }];
    }
}];

icn_cellAtIndexPathIsVisible: 方法只是 UICollectionView 上的一个类别:

- (BOOL) icn_cellAtIndexPathIsVisible:(NSIndexPath*)indexPath {

    BOOL __block wasVisible = NO;
    [self.indexPathsForVisibleItems enumerateObjectsUsingBlock:^(NSIndexPath* ip, NSUInteger idx, BOOL *stop) {

        if ([ip isEqual:indexPath]) {

            wasVisible = YES;
            *stop = YES;
        }
    }];

    return wasVisible;
}

更新: 这只适用于一个部分。

于 2014-05-13T11:08:48.943 回答
0

一个便宜的解决方案是添加另一个 0px 单元作为最后一个单元,并且永远不要删除它。

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
    if indexPath.row < collectibles.count - 1 { // dont delete the last one. just because the animation isn't working
        collectibles.removeAtIndex(indexPath.row)
        collectionView.deleteItemsAtIndexPaths([indexPath])
    }
}
于 2015-05-20T12:56:11.780 回答
0

我在图像集合视图中单行水平滚动时遇到了类似的问题。当我删除用于设置集合视图的代码时,问题消失了,contentSize它似乎可以自动处理这个问题。

不是一个特别冗长的答案,但我希望它有所帮助。

于 2015-07-16T09:36:00.450 回答