web-dev-qa-db-ja.com

UICollectionViewのセルを並べ替える

フローレイアウトと水平方向のUICollectionViewについて考えます。デフォルトでは、セルは上から下、左から右に並べられます。このような:

_1 4 7 10 13 16
2 5 8 11 14 17
3 6 9 12 15 18
_

私の場合、コレクションビューはページ化されており、特定の数のセルが各ページに収まるように設計されています。したがって、より自然な順序は次のようになります。

_1 2 3   10 11 12
4 5 6 - 13 14 15
7 8 9   16 17 18
_

これを達成する最も簡単な方法は、自分のカスタムレイアウトを実装するのではないでしょうか。特に、UICollectionViewFlowLayoutを使用して無料で提供される機能(アニメーションの挿入/削除など)を失いたくありません。

または、一般的に、フローレイアウトに並べ替え関数f(n)をどのように実装しますか?たとえば、右から左への順序付けにも同じことが当てはまります。

これまでの私の取り組み

私の最初のアプローチは、UICollectionViewFlowLayoutをサブクラス化し、_layoutAttributesForItemAtIndexPath:_をオーバーライドすることでした:

_- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSIndexPath *reorderedIndexPath = [self reorderedIndexPathOfIndexPath:indexPath];
    UICollectionViewLayoutAttributes *layout = [super layoutAttributesForItemAtIndexPath:reorderedIndexPath];
    layout.indexPath = indexPath;
    return layout;
}
_

ここで、_reorderedIndexPathOfIndexPath:_はf(n)です。 superを呼び出すことで、各要素のレイアウトを手動で計算する必要がなくなります。

さらに、表示する要素を選択するためにレイアウトが使用するメソッドである_layoutAttributesForElementsInRect:_をオーバーライドする必要がありました。

_- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *result = [NSMutableArray array];
    NSInteger sectionCount = 1;
    if ([self.collectionView.dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)])
    {
        sectionCount = [self.collectionView.dataSource numberOfSectionsInCollectionView:self.collectionView];
    }
    for (int s = 0; s < sectionCount; s++)
    {
        NSInteger itemCount = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:s];
        for (int i = 0; i < itemCount; i++)
        {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:s];
            UICollectionViewLayoutAttributes *layout = [self layoutAttributesForItemAtIndexPath:indexPath];
            if (CGRectIntersectsRect(rect, layout.frame))
            {
                [result addObject:layout];
            }
        }
    }
    return result;
}
_

ここでは、すべての要素を試してみて、指定されたrect内にある場合は、それを返します。

このアプローチが適切な方法である場合、次のより具体的な質問があります。

  • _layoutAttributesForElementsInRect:_オーバーライドを簡略化したり、より効率的な方法はありますか?
  • 何か不足していますか?少なくとも、異なるページのセルを交換すると、奇妙な結果が生じます。 _initialLayoutAttributesForAppearingItemAtIndexPath:_および_finalLayoutAttributesForDisappearingItemAtIndexPath:_に関連していると思いますが、何が問題なのか正確に特定できません。
  • 私の場合、f(n)は各ページの列と行の数に依存します。この情報をUICollectionViewFlowLayoutから抽出する方法はありますか?私は querying _layoutAttributesForElementsInRect:_ をコレクションビューの境界で考え、そこから行と列を推定しましたが、これも非効率的です。
42
hpique

私はあなたの質問についてたくさん考えて、以下の考慮事項に行きました:

FlowLayoutをサブクラス化することは、セルを並べ替えてフローレイアウトアニメーションを利用するための最も適切で最も効果的な方法のようです。そしてあなたのアプローチはうまくいきますが、2つの重要なことを除いて:

  1. セルが2つだけのコレクションビューがあり、9つのセルを含むことができるようにページをデザインしたとします。最初のセルは、元のフローレイアウトと同様に、ビューの左上隅に配置されます。ただし、2番目のセルはビューの一番上に配置する必要があり、インデックスパス[0、1]があります。並べ替えられたインデックスパスは[0、3](その場所にある元のフローレイアウトセルのインデックスパス)になります。 layoutAttributesForItemAtIndexPathオーバーライドでは、[super layoutAttributesForItemAtIndexPath:[0, 3]]のようなメッセージを送信します。セルは[0,0]と[0の2つしかないため、nilオブジェクトを取得します。 、1]。そして、これはあなたの最後のページの問題でしょう。
  2. targetContentOffsetForProposedContentOffset:withScrollingVelocity:をオーバーライドしてページング動作を実装し、itemSizeminimumLineSpacingminimumInteritemSpacingなどのプロパティを手動で設定することもできますが、アイテムを対称にするのは大変な作業です、ページング境界などを定義します。

必要なのはフローレイアウトではなくなったので、フローレイアウトをサブクラス化すると、多くの実装が準備されます。しかし、一緒に考えてみましょう。あなたの質問について:

  • あなたのlayoutAttributesForElementsInRect:オーバーライドは、元のApple実装がそうであるように正確であるため、それを単純化する方法はありません。しかし、あなたのケースでは、以下を検討することができます:3つの行がある場合ページごとのアイテム、および最初の行のアイテムのフレームが四角形のフレームと交差し、(すべてのアイテムが同じサイズの場合)2番目と3番目の行のアイテムのフレームがこの四角形と交差します。
  • すみません、2つ目の質問が理解できませんでした
  • 私の場合、並べ替え関数は次のようになります:(aは各ページの行/列のintegerの数、rows = columns)

f(n)=(n%a²)+(a-1)(col-row)+a²(n /a²); col =(n%a²)%a;行=(n%a²)/ a;

質問に答えると、フローレイアウトでは、各列の行数がわかりません。この数は、すべてのアイテムのサイズによって列ごとに異なる場合があるためです。各ページの列数についても、スクロール位置に依存し、変化する可能性があるため、何も言えません。したがって、layoutAttributesForElementsInRectをクエリするよりも良い方法はありませんが、これには部分的にしか表示されないセルも含まれます。セルのサイズは等しいので、理論的には、水平スクロール方向のコレクションビューの行数を確認できます。各セルの繰り返しを開始し、セルをカウントして、frame.Origin.xが変化した場合に分割します。

だから、あなたはあなたの目的を達成するために2つの選択肢があると思います:

  1. サブクラスUICollectionViewLayout。これらすべてのメソッドを実装するのは大変な作業のようですが、それが唯一の効果的な方法です。たとえば、itemSizeitemsInOneRowなどのプロパティを使用できます。次に、その番号に基づいて各項目のフレームを計算する式を簡単に見つけることができます(prepareLayoutで計算し、すべてのフレームを配列に格納して、必要なフレームにアクセスできないようにすることが最善の方法です) layoutAttributesForItemAtIndexPath)。 layoutAttributesForItemAtIndexPathlayoutAttributesForItemsInRectcollectionViewContentSizeの実装も非常に簡単です。 initialLayoutAttributesForAppearingItemAtIndexPathfinalLayoutAttributesForDisappearingItemAtIndexPathでは、alpha属性を0.0に設定するだけで済みます。これが標準のフローレイアウトアニメーションの仕組みです。 targetContentOffsetForProposedContentOffset:withScrollingVelocity:をオーバーライドすると、「ページング動作」を実装できます。

  2. フローレイアウト、pagingEnabled = YES、水平スクロール方向、およびアイテムサイズが画面サイズと等しいコレクションビューを作成することを検討してください。画面サイズごとに1つのアイテム。各セルに、垂直フローレイアウトのサブビューとして新しいコレクションビューを設定し、他のコレクションビューと同じデータソースをオフセット付きで設定できます。標準の方法で各セルを再利用する代わりに、9個(またはその他)のセルのブロックを含むコレクションビュー全体を再利用するため、非常に効率的です。すべてのアニメーションが正しく動作するはずです。

ここ レイアウトサブクラス化アプローチを使用してサンプルプロジェクトをダウンロードできます。 (#2)

15
boweidmann

スタンダートUICollectionViewFlowLayoutを使用して2コレクションビューを作成するのは簡単な解決策ではないでしょうか?

またはさらに良い:水平スクロールを備えたページビューコントローラーを使用すると、各ページは通常のフローレイアウトのコレクションビューになります。

アイデアは次のとおりです。UICollectionViewController -init methodで、2番目のコレクションビューを作成し、元のコレクションビューの幅で右にフレームオフセットを設定します。次に、それをサブビューとして元のコレクションビューに追加します。コレクションビューを切り替えるには、スワイプ認識機能を追加するだけです。オフセット値を計算するには、コレクションビューの元のフレームをivar cVFrameに保存します。コレクションビューを識別するには、タグを使用できます。

initメソッドの例:

CGRect cVFrame = self.collectionView.frame;
UICollectionView *secondView = [[UICollectionView alloc] 
              initWithFrame:CGRectMake(cVFrame.Origin.x + cVFrame.size.width, 0, 
                             cVFrame.size.width, cVFrame.size.height) 
       collectionViewLayout:[UICollectionViewFlowLayout new]];
    [secondView setBackgroundColor:[UIColor greenColor]];
    [secondView setTag:1];
    [secondView setDelegate:self];
    [secondView setDataSource:self];
    [self.collectionView addSubview:secondView];

UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc]
                      initWithTarget:self action:@selector(swipedRight)];
    [swipeRight setDirection:UISwipeGestureRecognizerDirectionRight];

UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] 
                       initWithTarget:self action:@selector(swipedLeft)];
    [swipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];

    [self.collectionView addGestureRecognizer:swipeRight];
    [self.collectionView addGestureRecognizer:swipeLeft];

swipeRightおよびswipeLeftメソッドの例:

-(void)swipedRight {
    // Switch to left collection view
    [self.collectionView setContentOffset:CGPointMake(0, 0) animated:YES];
}

-(void)swipedLeft {
    // Switch to right collection view
    [self.collectionView setContentOffset:CGPointMake(cVFrame.size.width, 0) 
                                 animated:YES];
}

そして、DataSourceメソッドを実装することは大きな問題ではありません(あなたのケースでは、各ページに9つの項目が必要です)。

-(NSInteger)collectionView:(UICollectionView *)collectionView 
    numberOfItemsInSection:(NSInteger)section {
    if (collectionView.tag == 1) {
         // Second collection view
         return self.dataArray.count % 9;
    } else {
         // Original collection view
         return 9; // Or whatever
}

メソッド-collectionView:cellForRowAtIndexPathでは、2番目のコレクションビューの場合、オフセットを使用してモデルからデータを取得する必要があります。

また、2番目のコレクションビューの再利用可能なセルのクラスも登録することを忘れないでください。また、ジェスチャ認識機能を1つだけ作成して、左右のスワイプを認識することもできます。それはあなた次第です。

私は今、うまくいくはずだと思います、それを試してみてください:-)

4
boweidmann

UICollectionViewDataSourceプロトコルを実装するオブジェクトがあります。内側collectionView:cellForItemAtIndexPath:返したい正しいアイテムを返すだけです。どこに問題があるのか​​わかりません。

編集:わかりました、問題が表示されます。これが解決策です: http://www.skeuo.com/uicollectionview-custom-layout-tutorial 、具体的にはステップ17から25までです。これはそれほどの作業ではなく、非常に簡単に再利用できます。

2
Rikkles

これは私の解決策です、私はアニメーションでテストしませんでしたが、少しの変更でうまくいくと思います、それが役に立てば幸いです

CELLS_PER_ROW = 4;
CELLS_PER_COLUMN = 5;
CELLS_PER_PAGE = CELLS_PER_ROW * CELLS_PER_COLUMN;

UICollectionViewFlowLayout* flowLayout = (UICollectionViewFlowLayout*)collectionView.collectionViewLayout;
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
flowLayout.itemSize = new CGSize (size.width / CELLS_PER_ROW - delta, size.height / CELLS_PER_COLUMN - delta);
flowLayout.minimumInteritemSpacing = ..;
flowLayout.minimumLineSpacing = ..;
..

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSInteger index = indexPath.row;
    NSInteger page = index / CELLS_PER_PAGE;
    NSInteger indexInPage = index - page * CELLS_PER_PAGE;
    NSInteger row = indexInPage % CELLS_PER_COLUMN;
    NSInteger column = indexInPage / CELLS_PER_COLUMN;

    NSInteger dataIndex = row * CELLS_PER_ROW + column + page * CELLS_PER_PAGE;

    id cellData = _data[dataIndex];
    ...
}
0
Henry Doan