2012-10-26 14 views
11

私はUICollectionViewを持っています。これは水平方向にスクロールし、アイテムの行が1つしかなく、ページングUIScrollViewのように動作します。私はSafariタブのピッカーの線に沿って何かを作っているので、各項目の端をまだ見ることができます。私は1つのセクションしか持っていません。UICollectionView水平スクロール、最後のアイテムの削除、アニメーションが機能しない

最後のアイテムではないアイテムを削除すると、すべてが正常に機能し、新しいアイテムが右側からスライドします。

最後のアイテムを削除すると、コレクションビューのスクロール位置がN-1番目のアイテムにスムーズにアニメーション化されずにジャンプし、N番目のアイテム(削除したアイテム)がフェードアウトします。

この動作は、私が作成したカスタムレイアウトとは無関係です。これは、プレーンフローレイアウトを使用するように切り替えても発生するためです。

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

他に誰かがこれを経験していますか? UICollectionViewのバグですか?回避策がありますか?

+0

を私もUICollectionViewを使用してサファリスタイルのタブピッカーを実施してきました。私は同じ不思議なアニメーションを見てきましたが、私はまだ何の回避策も見つけていません。 – rbrown

+0

'-shouldInvalidateLayoutForBoundsChange:'を実装して、最後の項目を削除するときはNOを返してください。これは私のSafariスタイルのタブのすべての問題を修正していませんが、いくつかのアニメーションに役立っています。 – rbrown

答えて

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スタイルのタブ選択ツールでお役に立てれば幸いです。あなたの実装は私のものとは異なることを知っています。私のソリューションがあなたにも役立つことを願っています。

+0

返信いただきありがとうございます!私はすぐにそれを試して、それがどのように行くかを教えてあげます。 –

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

これが役立ちます。

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:

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

更新: つのセクションでのみ動作方法は、UICollectionViewにちょうどカテゴリです。

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]) 
    } 
} 
0

イメージコレクションビューで1行の水平スクロールで同様の問題が発生しました。この問題は、コレクションビューのcontentSizeを設定するコードを削除したときに消えてしまい、自動的にこれを処理するようです。

特に冗長な回答ではありませんが、それが役に立ちそうです。

0

これは動作します:

collectionView.collectionViewLayout.invalidateLayout() 

collectionView.layoutIfNeeded() 
関連する問題