iOS7から、UICollectionViewのレイアウトを「アニメーションしながら」変更するために、以下の2種類のAPI群が追加されています。
- タップなどでアニメーションする場合
- 途中のコントロールが効かない代わりに簡単に使える
-
setCollectionViewLayout:animated:
(iOS6から) -
setCollectionViewLayout:animated:completion:
(iOS7から) - ジェスチャーなどでアニメーションする場合
- 途中のコントロールが可能で、「つまんで拡大縮小」といった事が出来る
-
startInteractiveTransitionToCollectionViewLayout:completion:
(iOS7から) -
finishInteractiveTransition
(iOS7から) -
cancelInteractiveTransition
(iOS7から)
今回、ピンチジェスチャーでUICollectionViewを拡大縮小したかったので、startInteractiveTransitionToCollectionViewLayout:completion:
なAPIを使って実装しました。
サンプルコード
処理の流れ
UICollectionViewのinteractiveTransitionとジェスチャーを組み合わせ、以下のような流れで処理が進みます。
- ジェスチャーの開始
- 次に表示するUICollectionViewLayoutオブジェクトを生成
-
startInteractiveTransitionToCollectionViewLayout:completion:
でTransitionを開始 - ジェスチャーの状態が変更
- PinchGestureの場合scaleの値がとれるので、これを元に0〜1の進捗(Progress)を計算し、UICollectionViewTransitionLayoutの
transitionProgress
にセットする - このprogressに応じて、元のレイアウトと新しいレイアウトの間の状態が自動で計算され、表示される
- ジェスチャーが終了
- progressの値に応じて、
finishInteractiveTransition
もしくはcancelInteractiveTransition
を呼び出す - ジェスチャーを一時停止する(B)
- (隙間時間が発生)(A)
- アニメーションの完了
- start時にしていたcompletionブロックが呼び出される
- ジェスチャーを再開する(B)
ここで注意するのは、ジェスチャーの終了からアニメーションの完了までに隙間時間(A)がある事です。progressが0.6等の半端な場合にfinishInteractiveTransition
やcancelInteractiveTransition
を呼び出すと、progress1.0になるまでアニメーションして(体感1秒程度かかるときがある)、その後にcompletionブロックが呼び出されます。
この隙間時間に次のtransitionを開始しようとするとクラッシュします。
次のTransitionが始まったら、隙間時間をすっ飛ばして次に行きたい所ですが、自分にはそのやり方がわからなかったので、ジェスチャーを一時停止する事にしました(B)。
ソースコードの抜粋
ジェスチャー部分だけ抜粋します。 色々なコードを逃がしているので、全体像はGithubを見てください。
#define kPAMProgressThreshold 0.5
-(void)pinchAction:(UIPinchGestureRecognizer *)gesture
{
switch(gesture.state){
case UIGestureRecognizerStateBegan:
{
NSLog(@"begin scale=%f",gesture.scale);
// 拡大なのか縮小なのかを記録しておく。
// これをしておかないと、拡大で開始して途中で縮小したりするとおかしな事になる。
self.zoomingStatus = gesture.pam_zoomStatus;
// Transitionする先のLayoutを用意
NSUInteger nextItemCount = [self nextHoraizontalItemCount];
UICollectionViewLayout *nextLayout=[UICollectionViewFlowLayout layoutWithHorizontalItemCount:nextItemCount];
// Transitionの開始
[self.collectionView startInteractiveTransitionToCollectionViewLayout:nextLayout
completion:^(BOOL completed, BOOL finish) {
NSLog( @"completion");
// Transitionの完了時にジェスチャーを止めるので、ここで再開
[self enableGesture];
}];
}
break;
case UIGestureRecognizerStateChanged:
// Transitionの進捗(progress)を0.0〜1.0で更新
self.transitionLayout.transitionProgress = [gesture pam_transitionProgressWithZoomStatus:self.zoomingStatus];
NSLog( @"transitionProgress=%f",self.transitionLayout.transitionProgress);
break;
case UIGestureRecognizerStateEnded:
// Transitionを完了
if( self.transitionLayout.transitionProgress > kPAMProgressThreshold ){
[self.collectionView finishInteractiveTransition];
self.currentHoraizontalItemCount = [self nextHoraizontalItemCount];
}else{
[self.collectionView cancelInteractiveTransition];
}
// 最後のアニメーションが終わるまでジェスチャーを停止
[self disableGesture];
break;
default:
break;
}
}
他のアニメーション
今回は1つのUICollectionViewControllerの中でレイアウトを変更しましたが、UIViewController間でのアニメーションも可能なようです。WWDC 2014のビデオ(要ログイン)の中の「Custom Transitions Using View Controllers」にいろいろ紹介されています。なかなかややこしそうですが、やれる事が増えそうです。