RFQuiltLayoutはそのままでも充分素敵なレイアウトクラスなのですが、今回はUICollectionViewLayoutの主だったメソッドの中でどのような処理を行っているか確認しつつ、ちょっとしたアニメーションを加えようかと思います。
レイアウト情報生成の流れ
1. prepareLayout
レイアウトの計算に必要となるような、前処理を行う。
- (void) prepareLayout { [super prepareLayout]; if (!self.delegate) return; BOOL cheap jerseys isVert = self.direction == UICollectionViewScrollDirectionVertical; cheap jerseys CGRect scrollFrame = CGRectMake(self.collectionView.contentOffset.x, self.collectionView.contentOffset.y, self.collectionView.frame.size.width, self.collectionView.frame.size.height); EASY int unrestrictedRow = 0; if (isVert) unrestrictedRow = (CGRectGetMaxY(scrollFrame) / [self blockPixels].height)+1; else unrestrictedRow = (CGRectGetMaxX(scrollFrame) / [self blockPixels].width)+1; [self fillInBlocksToUnrestrictedRow:self.prelayoutEverything? INT_MAX : unrestrictedRow]; }
まず、派生元のクラスの同メソッドを呼び出し規定の動作を行わせます。
[super prepareLayout];
delegateが設定されていないならなにもせずに終了です。設定がされていないまま計算は出来ませんのでなにもしません。
if (!self.delegate) return;
ビューの表示方向・コンテンツ表示領域・縦方向の最大表示ブロック数を計算・取得しています。
BOOL isVert = self.direction == UICollectionViewScrollDirectionVertical; CGRect scrollFrame = CGRectMake(self.collectionView.contentOffset.x, self.collectionView.contentOffset.y, self.collectionView.frame.size.width, self.collectionView.frame.size.height); int unrestrictedRow = 0; if (isVert) unrestrictedRow = (CGRectGetMaxY(scrollFrame) / [self blockPixels].height)+1; else unrestrictedRow = (CGRectGetMaxX(scrollFrame) / [self blockPixels].width)+1;
各セルの表示位置をブロック単位で計算してメンバ配列positionByIndexPathに格納しています。
[self fillInBlocksToUnrestrictedRow:self.prelayoutEverything? INT_MAX : unrestrictedRow];
2. collectionViewContentSize
コンテンツサイズを返します。
- (CGSize)collectionViewContentSize { BOOL isVert = self.direction == UICollectionViewScrollDirectionVertical; if (isVert) return CGSizeMake(self.collectionView.frame.size.width, (self.furthestBlockPoint.y+1) * self.blockPixels.height); else Google著作権侵害対策アップデート return CGSizeMake((self.furthestBlockPoint.x+1) * self.blockPixels.width, self.collectionView.frame.size.height); }
コンテンサイズを配置・ブロックサイズを考慮にいれ再計算しています。
3. prepareForCollectionViewUpdates
Cellの削除や追加等の処理が行なわれた際にのみ呼ばれます。削除・追加・移動処理のための前処理を定義します。
- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems { [super prepareForCollectionViewUpdates:updateItems]; for(UICollectionViewUpdateItem* item in updateItems) { if(item.updateAction == UICollectionUpdateActionInsert || item.updateAction wholesale mlb jerseys == UICollectionUpdateActionMove) { [self fillInBlocksToIndexPath:item.indexPathAfterUpdate]; } } }
派生元のクラスの同メソッドを呼び出し規定の動作を行った後に位置情報を再構成しています。
渡されるupdateItemsは、UICollectionViewUpdateItemオブジェクトからなるArrayです。updateActionプロパティから削除、挿入、移動などの更新処理内容を特定することができます。
4. layoutAttributesForElementsInRect
rectで指定された領域に存在するCell群のUICollectionViewLayoutAttributesオブジェクトをArrayで返します。
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { if (!self.delegate) return @[]; // see the comment on these properties if(CGRectEqualToRect(rect, self.previousLayoutRect)) { return self.previousLayoutAttributes; } self.previousLayoutRect = rect; BOOL isVert = self.direction == UICollectionViewScrollDirectionVertical; int unrestrictedDimensionStart = isVert? rect.origin.y / self.blockPixels.height : rect.origin.x / self.blockPixels.width; int unrestrictedDimensionLength = (isVert? rect.size.height / self.blockPixels.height : rect.size.width / self.blockPixels.width) + 1; int unrestrictedDimensionEnd = unrestrictedDimensionStart + unrestrictedDimensionLength; reklamowania [self fillInBlocksToUnrestrictedRow:self.prelayoutEverything? INT_MAX : unrestrictedDimensionEnd]; // find the indexPaths between those rows NSMutableSet* attributes = [NSMutableSet set]; [self traverseTilesBetweenUnrestrictedDimension:unrestrictedDimensionStart and:unrestrictedDimensionEnd iterator:^(CGPoint point) { NSIndexPath* indexPath = [self indexPathForPosition:point]; if(indexPath) [attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; return YES; }]; Stars return (self.previousLayoutAttributes = [attributes allObjects]); }
各種設定前の状態では空の配列を返しています。
if (!self.delegate) return @[];
前回の領域と今回取得したい領域が一致した場合には前回の結果を返しています。一致しない場合には今回の領域を保持しています。
// see the comment on these properties if(CGRectEqualToRect(rect, self.previousLayoutRect)) { return self.previousLayoutAttributes; } self.previousLayoutRect = rect;
領域内に存在する開始ブロック位置と終了ブロック位置などを計算し、UICollectionViewLayoutAttributesオブジェクトを取得して配列にして返しています。
BOOL isVert = self.direction == UICollectionViewScrollDirectionVertical; int unrestrictedDimensionStart = isVert? rect.origin.y / self.blockPixels.height : rect.origin.x / self.blockPixels.width; & int unrestrictedDimensionLength = (isVert? rect.size.height / self.blockPixels.height : rect.size.width / self.blockPixels.width) + 1; int unrestrictedDimensionEnd = unrestrictedDimensionStart + unrestrictedDimensionLength; [self fillInBlocksToUnrestrictedRow:self.prelayoutEverything? INT_MAX : unrestrictedDimensionEnd]; // find the indexPaths between those rows NSMutableSet* attributes = [NSMutableSet set]; [self traverseTilesBetweenUnrestrictedDimension:unrestrictedDimensionStart and:unrestrictedDimensionEnd iterator:^(CGPoint point) { NSIndexPath* indexPath = [self indexPathForPosition:point]; if(indexPath) [attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; return YES; }]; 平均時速の謎 return (self.previousLayoutAttributes = [attributes allObjects]);
5. layoutAttributesForItemAtIndexPath
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { UIEdgeInsets insets = UIEdgeInsetsZero; if([self.delegate respondsToSelector:@selector(insetsForItemAtIndexPath:)]) insets = [self.delegate insetsForItemAtIndexPath:indexPath]; CGRect frame = [self frameForIndexPath:indexPath]; UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; attributes.frame = UIEdgeInsetsInsetRect(frame, insets); return attributes; }
UICollectionViewLayoutAttributesを生成し取得したインセット情報を考慮した描画領域をframeプロパティに設定しています。
挿入・削除アニメーションの追加
ここからはカスタマイズの内容となりますので、GitHubにアップロードされているコードにはありません。
6. initialLayoutAttributesForAppearingItemAtIndexPath:
新しいCellを作成した際に、挿入アニメーションの方法を指定します。ここで渡されたUICollectionViewLayoutAttributesから、layoutAttributesForItemAtIndexPathで生成される挿入完了状態のattributesへとアニメーションします。今回は画面左側から所定の位置へスライドしつつフェードインするアニメーションにしてみました。
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath { UICollectionViewLayoutAttributes *attr = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath]; attr.center = CGPointMake(0 - attr.size.width / 2, attr.center.y); attr.alpha = 0.0; return attr; }
今回はセルを総入れ替えする形の実装をしているので記述されていませんが、このメソッドは既存のセルに対しても呼び出されるので、ひとつだけ挿入するといった実装の場合には対象のセルが元々存在するセルかを確認する必要があります。その場合には既存セルのレイアウト情報を取得して返してあげると既存位置から新しい位置への移動アニメーションになるはずです。
7. finalLayoutAttributesForDisappearingItemAtIndexPath:
Cellを削除した際に、削除アニメーションの方法を指定します。削除完了状態のセルのレイアウト情報を返します。今回はパラパラと崩れ落ちるアニメーションを実装しました。規定のレイアウト情報がalpha=0となっていますので、こちらも適用されて崩れ落ちつつフェードアウトしていきます。
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath { UICollectionViewLayoutAttributes *attr = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath]; Don srand((unsigned int)(time(nil) + itemIndexPath.row)); CGFloat vy = (rand() % 100) * 0.01; CGFloat vr = (rand() % 100) * 0.01; wholesale nba jerseys CATransform3D trans = CATransform3DIdentity; trans = CATransform3DTranslate(trans, -attr.size.width / 2, attr.size.height / 2, 0.0); trans = CATransform3DRotate(trans, M_PI_4 * vr, wholesale jerseys 0.0, 0.0, 1.0); trans = CATransform3DTranslate(trans, attr.size.width / 2, -attr.size.height / 2, 0.0); trans = CATransform3DTranslate(trans, 0.0, attr.size.height / 2 * vy, 0.0); attr.transform3D = trans; return attr; }
※この記事で使用したRFQuiltLayout.mは2013年11月27日のバージョンです。