前回に続いて、UICollectionViewFlowLayout拡張の話です。
UICollectionViewFlowLayoutの拡張
Sticky Sessionの実装方法
UICollectionViewFlowLayoutのレイアウトは静的なもので、スクロール時にレイアウトの再計算は必要ありません。しかし、Sticky SessionではHeader/Footerの位置がスクロールに応じて変化するため、スクロールに応じてレイアウトを再計算する必要があります。
このような場合には、shouldInvalidateLayoutForBoundsChangeからYESを返すことで、常にレイアウトを再計算することができます。
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
あとは頑張ってHeader/Footerの位置を計算するだけです。
contentInsets、sectionInsets、header/footerの有無を考えてコードを書く必要がありますが、順につぶしていけば実装できます。
パフォーマンス上の問題
このアプローチで実装すると、sticky view使用時のフレームレートが半分以下に落ちます。
いままで不要だったレイアウト計算が毎回必要になるので、当然の結果ですね。しかし、エンジニアとしてフレームレートが半分になるのは我慢できません。できませんよね?
解決策
原因がはっきりしている以上、解決策も簡単に思いつきます。スクロール時にはHeader/Footerのみを再計算すればいいわけです。これが思ったより面倒でした。
関係するコードを抜粋します。
鍵になるのは[self invalidationContextForBoundsChange:CGRectZero]で空のcontextを作っている部分です。これを元にheader/footerを追加することで、header/footerだけを更新するcontextを生成しています。
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
UICollectionViewLayoutInvalidationContext *context = [self invalidationContextForBoundsChange:CGRectZero];
[self invalidateLayoutWithContext:context];
return [super shouldInvalidateLayoutForBoundsChange:newBounds];
}
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForBoundsChange:(CGRect)newBounds
{
UICollectionViewLayoutInvalidationContext *context = [super invalidationContextForBoundsChange:newBounds];
NSInteger numOfSections = self.collectionView.numberOfSections;
NSMutableArray *indexePaths = NSMutableArray.new;
for(NSInteger s = 0 ; s < numOfSections ; s++ ){
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:s];
[indexePaths addObject:indexPath];
}
[context invalidateSupplementaryElementsOfKind:UICollectionElementKindSectionHeader
atIndexPaths:indexePaths];
[context invalidateSupplementaryElementsOfKind:UICollectionElementKindSectionFooter
atIndexPaths:indexePaths];
return context;
}
互換性
UICollectionViewLayoutInvalidationContextで、個別のSupplemental Viewを指定する機能はiOS8からしか使用できません。
そのためiOS7用ではパフォーマンスが落ちるかと思ったのですが、「指定できない==常にすべて更新」という意味らしく、新メソッドを呼び出さないようにするだけで動作しました。
最後に
BTKCollectionViewFlowLayoutのコードを見てもらうとわかりますが、実際には結構面倒な作業でした。Apache Licenseなので、よかったら使ってみてください。
BTKCollectionViewFlowLayout on GitHub
余談
PodspecがiOS6.0からになってる。。orz
iOS7.0以降でしか動きません。