UICollectionView горизонтальная прокрутка, удаление последнего элемента, анимация не работает
У меня есть UICollectionView. Он прокручивается по горизонтали, имеет только одну строку элементов и ведет себя как пейджинг UIScrollView. Я делаю что-то по строкам вкладки Safari, поэтому вы все равно можете видеть край каждого элемента. У меня есть только один раздел.
Если я удалю элемент, который не является последним, все работает так, как ожидалось, и новый элемент будет перемещаться справа.
Если я удаляю последний элемент, то позиция прокрутки в представлении коллекции переходит к N-1-му элементу (не плавно анимируется), а затем я вижу, что N-й элемент (тот, который я удалил) исчезает.
Это поведение не связано с настраиваемым макетом, который я сделал, поскольку это происходит, даже если я переключу его, чтобы использовать простой поток. Я удаляю элементы, используя:
[self.tabCollectionView deleteItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:index inSection:0]]];
Кто-нибудь еще испытал это? Это ошибка в UICollectionView, и есть ли способ обхода?
Ответы
Ответ 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:
не имеет блока завершения, поэтому мне пришлось имитировать его. Чтобы избежать проблем с синхронизацией, я отключил взаимодействие с пользователем. Это предотвращает взаимодействие пользователя с представлением коллекции перед удалением ячейки.
Еще одна вещь, которую я должен был наблюдать, - я должен reset вернуть свою ячейку к 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];
}
Предполагаемый выбор - это выбранный элемент, который вы удаляете. Это приведет к прокрутке к элементу слева от него. Обратите внимание, что оператор if проверяет, что это не единственный элемент. Если бы это был вызов, он бы разбился, поскольку не было -1 строки.
Затем вы можете реализовать следующий метод, который вызывается, когда анимация прокрутки завершена. Я просто устанавливаю isDeleting в no в методе deleteObjectInCollection, и все это работает.
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{
if(isDeleting){
[self deleteObjectInCollection];
}
}
Надеюсь, это поможет.
Ответ 3
И вот еще одно решение (targetIndexPath - это индексная ячейка ячейки, которую нужно удалить). Этот код можно просто поместить в метод removeCellAtIndexPath: (NSIndexPath *) targetIndexPath, и все готово (при условии, что мои адаптации от моего кода к общедоступному коду верны, в противном случае спросите меня, и я постараюсь помочь).
// (Here it 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;
}
Update:
Это работает только с одним разделом.
Ответ 4
Дешевое решение - добавить еще одну ячейку 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])
}
}
Ответ 5
Я столкнулся с аналогичной проблемой с горизонтальной прокруткой горизонтальной прокрутки по представлению коллекции изображений. Проблема исчезла, когда я удалил свой код для установки представления коллекции contentSize
, как представляется, обрабатывает это автоматически.
Не особенно подробный ответ, но я надеюсь, что это поможет.
Ответ 6
Это будет работать:
collectionView.collectionViewLayout.invalidateLayout()
collectionView.layoutIfNeeded()