我有一个UICollectionView。它水平滚动,只有一行项目,行为类似于分页UIScrollView。我正在制作Safari选项卡选项卡的内容,因此您仍然可以看到每个项目的边缘。我只有一个部分。
如果我删除的项目不是最后一项,那么一切都按预期工作,一个新项目从右侧滑入。
如果我删除了最后一项,那么集合视图的滚动位置会跳转到第N-1项(不能平滑地设置动画),然后我会看到第N项(我删除的项)淡出。
此行为与我所做的自定义布局无关,因为即使我将其切换为使用普通流布局也会发生这种情况。我正在删除这些项目:
[self.tabCollectionView deleteItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:index inSection:0]]];
还有其他人经历过这个吗?它是UICollectionView中的错误,是否有解决方法?
答案 0 :(得分: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风格的标签选择器。我知道你的实现与我的不同,我希望我的解决方案也适合你。
答案 1 :(得分: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];
}
假设选择是您要删除的所选项目。这将滚动到它左侧的项目。请注意if语句检查这不是唯一的项目。如果是,那么调用就会崩溃,因为没有-1行。
然后,您可以实现以下方法,该方法在滚动动画完成时调用。我只是在deleteObjectInCollection方法中将isDeleting设置为no,这一切似乎都有效。
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{
if(isDeleting){
[self deleteObjectInCollection];
}
}
我希望这会有所帮助。
答案 2 :(得分: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;
}
<强>更新强> 这仅适用于一个部分。
答案 3 :(得分:0)
一个便宜的解决方案是添加另一个0px单元格作为最后一个单元格,永远不会删除它。
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let detail = segue.destinationViewController as! detailSkinsViewController {
detail.skinNameLabel = self.skin
if (self.skin == "AK47 | Wasteland Rebel") {
detail.skinImg.image = UIImage(named: "s495fn")
}
}
}
答案 4 :(得分:0)
我遇到了类似的问题,单行横向滚动图像集合视图。当我删除用于设置集合视图的代码时,问题就消失了contentSize
它似乎自动处理了这个问题。
不是特别冗长的答案,但我希望它有所帮助。
答案 5 :(得分:0)
这将有效:
collectionView.collectionViewLayout.invalidateLayout()
collectionView.layoutIfNeeded()