13

UICollectionViewがあります。水平方向にスクロールし、アイテムの行が1つだけで、ページングUIScrollViewのように動作します。私はSafariタブピッカーのラインに沿って何かを作っているので、あなたはまだ各アイテムの端を見ることができます。セクションは1つだけです。

最後のアイテムではないアイテムを削除すると、すべてが期待どおりに機能し、新しいアイテムが右からスライドインします。

最後のアイテムを削除すると、コレクションビューのスクロール位置が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:完了ブロックがないため、シミュレートする必要がありました。タイミングの問題を避けるために、ユーザーの操作を無効にしました。これにより、ユーザーはセルが削除される前にコレクション ビューを操作できなくなります。

私が注意しなければならなかったもう 1 つのことは、セルの再利用のためにセルのアルファを 1 にリセットしなければならないことです。

これが Safari スタイルのタブ ピッカーの役に立てば幸いです。あなたの実装が私の実装とは異なることはわかっています。私のソリューションがあなたにも役立つことを願っています。

于 2012-11-15T18:24:35.080 に答える
1

これにはすでに答えがあることは知っていますが、設定された間隔の後にディスパッチする必要のない、少し異なる方法でこれを実装しました。

delete メソッドでは、最後のアイテムが削除されたかどうかを判断するためにチェックを行います。次のように呼び出した場合:

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

これが役立つことを願っています。

于 2013-10-22T19:20:17.597 に答える
1

これはうまくいきます:

collectionView.collectionViewLayout.invalidateLayout()

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

画像コレクションビューを1行水平スクロールすると、同様の問題に直面していました。コレクションビューを設定するためのコードを削除contentSizeすると、これは自動的に処理されるようです。

特に冗長な回答ではありませんが、お役に立てば幸いです。

于 2015-07-16T09:36:00.450 に答える
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;
}

更新: これは 1 つのセクションでのみ機能します。

于 2014-05-13T11:08:48.943 に答える