[iOS 7] [UI Dynamics] UIDynamicAnimatorとビヘイビアの使い方〜UIGravityBehavior〜

前回のiOS 7新機能!物理学的な難しい知識無しにアニメーションを実装できるようになった!では自由落下運動と地面でバウンドするアニメーションのサンプルを書きました。今回はUIGravityBehaviorの使い方や、UIDynamicAnimatorについてもう少し詳しく書きたいと思います。

前回のおさらい

まず、前回の記事のソースコードを見てみましょう。

ViewController.m
#import "ViewController.h"

@interface ViewController ()

@property (nonatomic) UIDynamicAnimator *animator;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // アニメーションさせるビュー(赤い四角)
    UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(110.0, 0.0, 100.0, 100.0)];
    myView.backgroundColor = [UIColor redColor];
    [self.view addSubview:myView];

    // UIDynamicAnimator!今回の主役
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

    // UIDynamicAnimatorの仲間たち1。重力を司る者
    UIGravityBehavior *gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:@[myView]];

    // UIDynamicAnimatorの仲間たち2。衝突を司る者
    UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[myView]];
    collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;

    // アニメーターにビヘイビアを登録
    [self.animator addBehavior:gravityBeahvior];
    [self.animator addBehavior:collisionBehavior];
}

@end

UIDynamicAnimator

UIDynamicAnimatorは今回の主役とも言える、アニメーションを管理するクラスです。UIDynamicAnimatorのインスタンスは- (instancetype)initWithReferenceView:(UIView *)viewメソッドで生成します。サンプルでは21行目にあります。このメソッドの引数に指定するビューはアニメーションを行うための土台です。座標の計算などはこのビューを中を基準に行われます。

ビヘイビアの追加

ビヘイビアの追加は- (void)addBehavior:(UIDynamicBehavior *)behaviorメソッドを使用します(サンプルの31、32行目)。逆に、削除する場合は- (void)removeBehavior:(UIDynamicBehavior *)behaviorメソッドか- (void)removeAllBehaviorsを使用します。

UIDynamicAnimatorDelegateプロトコル

UIDynamicAnimatorではデリゲートも用意されています。試しに以下のようにViewController.mを修正してみましょう。

ViewController.m
#import "ViewController.h"

@interface ViewController () <UIDynamicAnimatorDelegate>

@property (nonatomic) UIDynamicAnimator *animator;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // アニメーションさせるビュー(赤い四角)
    UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(110.0, 0.0, 100.0, 100.0)];
    myView.backgroundColor = [UIColor redColor];
    [self.view addSubview:myView];

    // UIDynamicAnimator!今回の主役
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    self.animator.delegate = self;

    // UIDynamicAnimatorの仲間たち1。重力を司る者
    UIGravityBehavior *gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:@[myView]];

    // UIDynamicAnimatorの仲間たち2。衝突を司る者
    UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[myView]];
    collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;

    // アニメーターにビヘイビアを登録
    [self.animator addBehavior:gravityBeahvior];
    [self.animator addBehavior:collisionBehavior];
}

- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator
{
    NSLog(@"%s", __func__);
}

- (void)dynamicAnimatorWillResume:(UIDynamicAnimator *)animator
{
    NSLog(@"%s", __func__);
}

@end

実行すると、以下のようにアニメーション開始時アニメーション終了時にログが出力されるはずです。

ios7-uidynamic-2-1

UIDynamicAnimatorが解放されないように気をつけよう!

サンプルの5行目でもあるように、このサンプルではUIDynamicAnimatorインスタンスをプロパティとして宣言し、保持しています。こうしないとアニメーションが実行されないので注意してください。

// プロパティを用意して・・・
@property (nonatomic) UIDynamicAnimator *animator;

// 保持しておく!
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

UIGravityBehavior

UIGravityBehaviorは文字通り重力に従ったアニメーションを行うためのクラスです。UIGravityBehaviorだけを使用した例を見てみましょう。
ViewController.mを以下のように変更してみましょう。

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic) UIDynamicAnimator *animator;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // アニメーションさせるビュー(赤い四角)
    UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(110.0, 0.0, 100.0, 100.0)];
    myView.backgroundColor = [UIColor redColor];
    [self.view addSubview:myView];

    // UIDynamicAnimator!今回の主役
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

    // UIDynamicAnimatorの仲間たち1。重力を司る者
    UIGravityBehavior *gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:@[myView]];

    // アニメーターにビヘイビアを登録
    [self.animator addBehavior:gravityBeahvior];
}

@end

実行すると、赤い四角形のビューが上から落ちてきてそのまま画面の外にいなくなってしまいましたね。このように落下運動はUIGravityBehaviorが実現していたというわけです。

ios7-uidynamic-2-4

アニメーションさせるビューを複数指定する

UIGravityBehaviorのイニシャライザメソッドである- (instancetype)initWithItems:(NSArray *)itemsを見て分かるように、アニメーションさせるビューは複数指定することができます。試しにViewController.mを以下のように変更してみましょう。

ViewController.m
#import "ViewController.h"

@interface ViewController ()

@property (nonatomic) UIDynamicAnimator *animator;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // アニメーションさせるビュー(赤い四角と青い四角)
    UIView *myView1 = [[UIView alloc] initWithFrame:CGRectMake(50.0, 0.0, 100.0, 100.0)];
    myView1.backgroundColor = [UIColor redColor];
    [self.view addSubview:myView1];

    UIView *myView2 = [[UIView alloc] initWithFrame:CGRectMake(170.0, 0.0, 100.0, 100.0)];
    myView2.backgroundColor = [UIColor blueColor];
    [self.view addSubview:myView2];

    // UIDynamicAnimator!今回の主役
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

    // UIDynamicAnimatorの仲間たち1。重力を司る者
    UIGravityBehavior *gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:@[myView1, myView2]];

    // アニメーターにビヘイビアを登録
    [self.animator addBehavior:gravityBeahvior];
}

@end

実行すると、赤と青のビューが同じように落下するはずです。

ios7-uidynamic-2-3

gravityDirection

UIGravityBehaviorにはgravityDirectionプロパティがあります。このプロパティを設定することによって重力がかかる方向(重力のベクトル)を変更することができます。値はCGVectorで指定します。CGVectorもiOS 7より新しく追加になったやつで、二次元ベクトルを表す構造体だそうです。

UIGravityBehavior *gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:@[myView]];
gravityBeahvior.gravityDirection = CGVectorMake(-1.0, -1.0); // デフォルトは(0.0, 1.0)

ios7-uidynamic-2-2

次回、UICollisionBehaviorについて詳しく!

このようにとても簡単に重力運動のアニメーションが実装できました。 ただ、このままだと赤い四角形のビューが画面の外にいったままいなくなってしまいますね。地面(=画面の境界)にきたらバウンドさせるにはどうしたら良いでしょうか?
そう!UICollisionBehaviorの登場です。
というわけで、次回は衝突に関する設定を行うビヘイビアクラスであるUICollisionBehaviorについて詳しく書きたいと思います!