フローレイアウトと水平方向の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:
_ をコレクションビューの境界で考え、そこから行と列を推定しましたが、これも非効率的です。私はあなたの質問についてたくさん考えて、以下の考慮事項に行きました:
FlowLayoutをサブクラス化することは、セルを並べ替えてフローレイアウトアニメーションを利用するための最も適切で最も効果的な方法のようです。そしてあなたのアプローチはうまくいきますが、2つの重要なことを除いて:
layoutAttributesForItemAtIndexPath
オーバーライドでは、[super layoutAttributesForItemAtIndexPath:[0, 3]]
のようなメッセージを送信します。セルは[0,0]と[0の2つしかないため、nil
オブジェクトを取得します。 、1]。そして、これはあなたの最後のページの問題でしょう。targetContentOffsetForProposedContentOffset:withScrollingVelocity:
をオーバーライドしてページング動作を実装し、itemSize
、minimumLineSpacing
、minimumInteritemSpacing
などのプロパティを手動で設定することもできますが、アイテムを対称にするのは大変な作業です、ページング境界などを定義します。必要なのはフローレイアウトではなくなったので、フローレイアウトをサブクラス化すると、多くの実装が準備されます。しかし、一緒に考えてみましょう。あなたの質問について:
layoutAttributesForElementsInRect:
オーバーライドは、元のApple実装がそうであるように正確であるため、それを単純化する方法はありません。しかし、あなたのケースでは、以下を検討することができます:3つの行がある場合ページごとのアイテム、および最初の行のアイテムのフレームが四角形のフレームと交差し、(すべてのアイテムが同じサイズの場合)2番目と3番目の行のアイテムのフレームがこの四角形と交差します。f(n)=(n%a²)+(a-1)(col-row)+a²(n /a²); col =(n%a²)%a;行=(n%a²)/ a;
質問に答えると、フローレイアウトでは、各列の行数がわかりません。この数は、すべてのアイテムのサイズによって列ごとに異なる場合があるためです。各ページの列数についても、スクロール位置に依存し、変化する可能性があるため、何も言えません。したがって、layoutAttributesForElementsInRect
をクエリするよりも良い方法はありませんが、これには部分的にしか表示されないセルも含まれます。セルのサイズは等しいので、理論的には、水平スクロール方向のコレクションビューの行数を確認できます。各セルの繰り返しを開始し、セルをカウントして、frame.Origin.x
が変化した場合に分割します。
だから、あなたはあなたの目的を達成するために2つの選択肢があると思います:
サブクラスUICollectionViewLayout
。これらすべてのメソッドを実装するのは大変な作業のようですが、それが唯一の効果的な方法です。たとえば、itemSize
、itemsInOneRow
などのプロパティを使用できます。次に、その番号に基づいて各項目のフレームを計算する式を簡単に見つけることができます(prepareLayout
で計算し、すべてのフレームを配列に格納して、必要なフレームにアクセスできないようにすることが最善の方法です) layoutAttributesForItemAtIndexPath
)。 layoutAttributesForItemAtIndexPath
、layoutAttributesForItemsInRect
、collectionViewContentSize
の実装も非常に簡単です。 initialLayoutAttributesForAppearingItemAtIndexPath
とfinalLayoutAttributesForDisappearingItemAtIndexPath
では、alpha属性を0.0に設定するだけで済みます。これが標準のフローレイアウトアニメーションの仕組みです。 targetContentOffsetForProposedContentOffset:withScrollingVelocity:
をオーバーライドすると、「ページング動作」を実装できます。
フローレイアウト、pagingEnabled = YES
、水平スクロール方向、およびアイテムサイズが画面サイズと等しいコレクションビューを作成することを検討してください。画面サイズごとに1つのアイテム。各セルに、垂直フローレイアウトのサブビューとして新しいコレクションビューを設定し、他のコレクションビューと同じデータソースをオフセット付きで設定できます。標準の方法で各セルを再利用する代わりに、9個(またはその他)のセルのブロックを含むコレクションビュー全体を再利用するため、非常に効率的です。すべてのアニメーションが正しく動作するはずです。
ここ レイアウトサブクラス化アプローチを使用してサンプルプロジェクトをダウンロードできます。 (#2)
スタンダート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つだけ作成して、左右のスワイプを認識することもできます。それはあなた次第です。
私は今、うまくいくはずだと思います、それを試してみてください:-)
UICollectionViewDataSource
プロトコルを実装するオブジェクトがあります。内側collectionView:cellForItemAtIndexPath:
返したい正しいアイテムを返すだけです。どこに問題があるのかわかりません。
編集:わかりました、問題が表示されます。これが解決策です: http://www.skeuo.com/uicollectionview-custom-layout-tutorial 、具体的にはステップ17から25までです。これはそれほどの作業ではなく、非常に簡単に再利用できます。
これは私の解決策です、私はアニメーションでテストしませんでしたが、少しの変更でうまくいくと思います、それが役に立てば幸いです
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];
...
}