UICollectionView 水平滚动,删除最后一项,动画不起作用
Posted
技术标签:
【中文标题】UICollectionView 水平滚动,删除最后一项,动画不起作用【英文标题】:UICollectionView horizontal scrolling, deleting last item, animation not working 【发布时间】:2012-10-26 14:45:46 【问题描述】:我有一个 UICollectionView。它水平滚动,只有一行项目,并且表现得像一个分页 UIScrollView。我正在按照 Safari 选项卡选择器的方式制作一些东西,因此您仍然可以看到每个项目的边缘。我只有一个部分。
如果我删除一个不是最后一个项目的项目,一切都会按预期进行,并且一个新项目会从右侧滑入。
如果我删除最后一项,则集合视图的滚动位置会跳转到第 N-1 项(动画不流畅),然后我看到第 N 项(我删除的那个)淡出。
此行为与我制作的自定义布局无关,因为即使我将其切换为使用普通流布局也会发生这种情况。我正在使用以下方式删除项目:
[self.tabCollectionView deleteItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:index inSection:0]]];
有其他人经历过吗?是不是 UICollectionView 的 bug,有解决办法吗?
【问题讨论】:
我也一直在使用 UICollectionView 实现一个 Safari 风格的选项卡选择器。我一直在看到同样奇怪的动画,但我还没有找到任何解决方法。 您应该尝试实现-shouldInvalidateLayoutForBoundsChange:
并在删除最后一项时返回NO。这并没有解决我的 Safari 样式标签的所有问题,但它对一些动画有所帮助。
你在这里成功了吗。我在 2019 年遇到了同样的问题,哈哈。
【参考方案1】:
我设法使用标准 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 风格的标签选择器。我知道您的实现与我的不同,我希望我的解决方案也适用于您。
【讨论】:
感谢您的回复!我会尽快试一试,然后告诉你进展如何。【参考方案2】:我知道这已经有了答案,但我以稍微不同的方式实现了它,不需要在设定的时间间隔后调度。
在您的删除方法中,您将进行检查以确定最后一项是否被删除。如果是这样调用:
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];
我希望这会有所帮助。
【讨论】:
【参考方案3】:这将起作用:
collectionView.collectionViewLayout.invalidateLayout()
collectionView.layoutIfNeeded()
【讨论】:
【参考方案4】:还有另一种解决方案(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;
更新: 这仅适用于一个部分。
【讨论】:
【参考方案5】:一种廉价的解决方案是添加另一个 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])
【讨论】:
【参考方案6】:我在图像集合视图中单行水平滚动时遇到了类似的问题。当我删除用于设置集合视图 contentSize
的代码时,问题消失了,它似乎可以自动处理这个问题。
不是一个特别冗长的答案,但我希望它有所帮助。
【讨论】:
以上是关于UICollectionView 水平滚动,删除最后一项,动画不起作用的主要内容,如果未能解决你的问题,请参考以下文章