facebook/pop を使ってみた

最近 FacebookがOSSとして公開した [pop](https://github.com/facebook/pop) は
[Paper](https://www.facebook.com/paper)で使われているアニメーションライブラリです

<https://github.com/facebook/pop>

少し触ってみたので基本的な使い方などをまとめてみます

[**この記事はブログでも公開しています**](http://tatyusa.logdown.com/posts/196841/facebook-pop)

##pop とは
公式ブログに記事があるのでそちらがとても参考になります
[Introducing Pop, the animation engine behind Paper](https://code.facebook.com/posts/234067533455773/introducing-pop-the-animation-engine-behind-paper/)

iOS や OS X でアニメーションを実装しようとするとCoreAnimationを使うと思うのですが実はCoreAnimationはなかなか使いにくいライブラリだったりします。確かに`CABasicAnimation`は簡単ですが、Timing Functionを`linear`, `ease-in`, `ease-out`, `ease-in ease-out`の四種類と3次ベジェ曲線のいずれかからしか指定することができません。これだとバネだとかバウンドだとかの表現が出来ませんよね。一方`CAKeyframeAnimation`は万能ですが、パスを逐一作らなければならず手間がかかりコードも煩雑になります。

pop は`CABasicAnimation`の簡単さを保ちつつ`CAKeyframeAnimation`並みの表現力を持ったアニメーションを作れるAPIを提供しています。またそのAPIもCoreAnimationに似せていて学習コストを下げる工夫がされています。
pop の目標として
* よく使われるアニメーションを簡単に書けるようにすること
* 拡張可能なフレームワークであること
* 開発者にとって使いやすく強力なプログラミングモデルを構築すること

の3つが掲げてあり、今後もFacebookが中心となってメンテされていくことを考えると安心して使って行けます。

##基本的な使い方
###導入
Cocoapods経由でインストールします（もちろん直接プロジェクトに追加することも可能です）。適当なXcodeプロジェクトのルートフォルダに`Podfile`を作成して

```
pod 'pop', '~> 1.0'
```
の行を追加し`pod install`します。これで準備完了です☆
あとは使いたいファイルの先頭に

```
#import "pop/pop.h"
```

を追加しましょう。

###POPSpringAnimation
![](http://gyazo.com/711a6845ad983541b3acc72348817d29.gif)
`POPSpringAnimation`はpopの威力を知るのに一番簡単でとても強力な例になるでしょう。

まずはアニメーションさせる対象を作ります

```objc
_view = [[UIView alloc] initWithFrame:CGRectMake(200,200,80,80)];
[_view setBackgroundColor:[UIColor cyanColor]];
```
これに対し適当なタイミングでアニメーションオブジェクトを作成して登録してやることでアニメーションを発生させます。
例えば以下のようにします

```objc
POPSpringAnimation *animation = [POPSpringAnimation animation];
animation.property = [POPAnimatableProperty propertyWithName:kPOPLayerSize];
animation.toValue = [NSValue valueWithCGSize:CGSizeMake(100, 100)];
    
animation.springBounciness = 24.0f;
animation.springSpeed = 6.0f;
    
[_view pop_addAnimation:animation forKey:@"bound"];
```

これで対象の大きさをバネのような弾性を持ったアニメーションで変化させることが出来ます。
例としてサイズの変更を挙げましたが`POPAnimatableProperty propertyWithName:`の引数の定数を変えることで色々なプロパティを変化させることが出来ます。用意されている定数の一覧は[POPAnimatableProperty.h](https://github.com/facebook/pop/blob/master/pop/POPAnimatableProperty.h)で見ることが出来ます。

以上の断片的なコードをまとめて以下の様なViewControllerの実装を作ればすぐにでも試すことが出来ます。
ビルドしたら画面をタップしてみてください！

```objc
#import "pop/pop.h"
#import "ViewController.h"

@implementation ViewController
{
    UIView *_view;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    _view = [[UIView alloc] initWithFrame:CGRectMake(120, 120, 80, 80)];
    [_view setBackgroundColor:[UIColor cyanColor]];
    
    [self.view addSubview:_view];
    
    [self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(startAnimation)]];
}

-(void) startAnimation
{
    POPSpringAnimation *animation = [POPSpringAnimation animation];
    animation.property = [POPAnimatableProperty propertyWithName:kPOPLayerSize];
    animation.toValue = [NSValue valueWithCGSize:CGSizeMake(100, 100)];
    
    animation.springBounciness = 24.0f;
    animation.springSpeed = 6.0f;
    
    [_view pop_addAnimation:animation forKey:@"spring"];
}

@end
```

パラメータを調節すればもっと激しい動きをさせることも出来ますよ！
[参考: Playing with Pop (ii)](http://codeplease.io/playing-with-pop-ii/)

###POPDecayAnimation
![](http://gyazo.com/031c7a9ed4eb78d90c523f57552d33bc.gif)
`POPDecayAnimation`は`ease-out`よりももっとゆっくり変化がとまるようなアニメーションです。
先ほどの`startAnimation`を例えば以下のように変更してみてください

```objc
-(void) startAnimation
{
    POPDecayAnimation *animation = [POPDecayAnimation animation];
    animation.property = [POPAnimatableProperty propertyWithName:kPOPLayerSize];
    
    animation.velocity = [NSValue valueWithCGSize:CGSizeMake(100, 100)];
    
    [_view pop_addAnimation:animation forKey:@"decay"];
}
```

`POPDecayAnimation`は少し癖があって例えば`toValue`のプロパティを設定するとエラーを吐きます。
これはこのオブジェクトが`fromValue`と`velocity`と`deceleration`の3つのプロパティから`duration`と`toValue`を自動的に決定するからです。なので使う時は前の3つを設定してやれば使うことが出来ます（実際には`fromValue`と`deceleration`はデフォルト値が存在するので省略可能です）。

###POPCustomAnimation
![](http://gyazo.com/b09f1c6d70fd55da72233db95c1ff430.gif)
`POPCustomAnimation`は以上3つのクラスとは違って自分でオリジナルのアニメーションを作るためのクラスです。
実際にどうやって作るのが正しいのかわかりませんが、実装をみながら簡単なアニメーションを作ってみたいと思います。

アニメーションといえばバウンドの表現もよく使うと思うのですがこれは標準では用意されていないようです。今回は`POPCustomAnimation`を使ってバウンドのアニメーションを実装してみたいと思います。

```objc
-(void) startAnimation
{
    float duration = 0.6;
    CGPoint fromValue = _view.frame.origin;
    CGPoint toValue = CGPointMake(120,240);
    
    float dx = toValue.x - fromValue.x;
    float dy = toValue.y - fromValue.y;
    
    POPCustomAnimation *animation = [POPCustomAnimation animationWithBlock:^BOOL(id target, POPCustomAnimation *animation) {
        float t = (animation.currentTime-animation.beginTime)/duration;
        
        float d = 8/pow(2, floor(log2(1 + 11*(1-t))));
        float timing = pow(t*11/4-3*(1-1/d), 2) + (1-1/d)*(1+1/d);
        
        UIView *view = (UIView *)target;
        CGSize size = view.frame.size;
        
        CGRect after = CGRectMake(fromValue.x + timing * dx, fromValue.y + timing * dy, size.width, size.height);
        
        [view setFrame:after];
        
        return t < 1.0;
    }];
    
    [_view pop_addAnimation:animation forKey:@"bounce"];
}
```

`POPCustomAnimation animationWithBlock:`のブロックはアニメーションの各フレームで呼び出されるのでその中で`target`がどのように変化するのかを記述しています。
ブロックの引数の`animation`からは

* beginTime - 開始時間
* currentTime - 現在の時間
* elapsedTime - 1ステップにかかった時間

が取得できるのでこれらの値を使って`target`のプロパティを更新して行きます。
今の場合`target`には`_view`が入っています。

ブロックの返り値はアニメーションを続けるかどうかの真偽値を返します。
今の場合は`t`が1.0より小さい時は`YES`が返ってアニメーションが継続され、`t`が1.0より大きくなると`NO`が返ってその時点でアニメーションが停止します。


###POPBasicAnimation
![](http://gyazo.com/57ae7239e4729d2030e8700e11154712.gif)
`POPBasicAnimation`は`CABasicAnimation`と同等のアニメーションを置き換えたクラスになっています。

```objc
-(void) startAnimation
{
    POPBasicAnimation *animation = [POPBasicAnimation animation];
    //POPBasicAnimation *animation = [POPBasicAnimation easeInEaseOutAnimation];
    animation.property = [POPAnimatableProperty propertyWithName:kPOPLayerSize];
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    animation.toValue = [NSValue valueWithCGSize:CGSizeMake(100, 100)];
    
    [_view pop_addAnimation:animation forKey:@"basic"];
}
```

コメントアウトしている

```objc
POPBasicAnimation *animation = [POPBasicAnimation easeInEaseOutAnimation];
```

は

```objc
POPBasicAnimation *animation = [POPBasicAnimation animation];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
```
と[全く同等の処理](https://github.com/facebook/pop/blob/master/pop/POPBasicAnimation.mm#L57)になっています。

##まとめ
以上ざっと pop の基本的な使い方を見てきました。
紹介したのは

* POPSpringAnimation
* POPDecayAnimation
* POPCustomAnimation
* POPBasicAnimation

の四つだけでしたがどれもとても簡単に使えたと思います。
pop には他にも

* POPPropertyAnimation
* POPAnimatableProperty

などのクラスがありまだまだ出来ることは多そうです。

----
pop はBSDライセンスで公開されています。
<https://github.com/facebook/pop>