2014/01/18

UIKit Dynamicsを使ってみるチュートリアル

UIKit Dynamicsを使う簡単なチュートリアルを書いてみました。

箱に重力を追加する

まずは箱をひとつ作って、それを重力をつけて下に落としてみます。

Xcodeで「Single View Application」なプロジェクトを新規作成します。

灰色で、64x64の大きさのUIViewの箱を上のほうにつくってOutletに接続します。

続いて、重力を付けるように実装します。

UDSViewController.mにあるビューコントローラの無名カテゴリにanimatorgravityを追加します。

@interface UDSViewController ()
@property UIDynamicAnimator* animator;
@property UIGravityBehavior* gravity;
@end

そして、viewDidLoadでこれらを初期化します。

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    _animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    _gravity = [[UIGravityBehavior alloc] initWithItems:@[self.firstBox]];

    [_animator addBehavior:_gravity];
}

これだけで、実行すると上のほうにある箱が下に落ちていきます。

床を作る

今のままだと、下に床がないので箱がそのまま消えてしまいます。

なので、床を足してみましょう。

まず、無名カテゴリにcollisionを追加します。

@interface UDSViewController ()
@property UIDynamicAnimator* animator;
@property UIGravityBehavior* gravity;
@property UICollisionBehavior* collision;
@end

そして、viewDidLoadに初期化コードを追加します。

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    _animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    _gravity = [[UIGravityBehavior alloc] initWithItems:@[self.firstBox]];
    _collision = [[UICollisionBehavior alloc] initWithItems:@[self.firstBox]];

    [_animator addBehavior:_gravity];
    [_animator addBehavior:_collision];
}

さらに、viewDidLoadの下に次のコードを追加します。

    CGSize s = self.view.frame.size;
    CGFloat w = s.width;
    CGFloat h = s.height;

    [_collision addBoundaryWithIdentifier:@"bottom"
                                fromPoint:CGPointMake(0, h)
                                  toPoint:CGPointMake(w, h)];

これで、下に床が追加されました。

クラス同士の関係

この時点でのクラス同士の関係を次に示します。

UIDynamicAnimatorは、「重力」や「衝突」といった挙動をプラグイン化したUIDynamicBehaviorのサブクラスを持つことができ、このサブクラスは、その挙動をするアイテムを持っています。このアイテムはUIDynamicItemプロトコルに従う必要があります。

他のオブジェクトを作る

続いて、タップで新規オブジェクトを追加するようにしてみます。

まず、ストーリボード上のトップレベルのUIViewのCustom ClassをUIButtonにします。

そして、それに対するタップ動作を次のように定義します。

そして、その実装では次のように書きます。

- (IBAction)onViewTapped:(UIButton *)sender forEvent:(UIEvent *)event {
    NSSet* set = [event touchesForView:sender];
    CGPoint p = [set.allObjects[0] locationInView:sender];

    UIView* box = [UIView.alloc initWithFrame:CGRectMake(p.x, p.y, 48, 48)];
    box.layer.backgroundColor = UIColor.grayColor.CGColor;

    [self.view addSubview:box];
    [_gravity addItem:box];
    [_collision addItem:box];
}

追加した箱に重力と衝突の両方の挙動をさせたいので_gravity_collisionの両方でaddItem:をしています。

つるつるにする

物体の跳ねかたや表面のつるつる具合を変更するにはUIDynamicItemBehaviorを使います。

まず、無名カテゴリにdynamicPropertiesを追加します。

@interface UDSViewController ()
@property UIDynamicAnimator* animator;
@property UIGravityBehavior* gravity;
@property UICollisionBehavior* collision;
@property UIDynamicItemBehavior* dynamicProperties;
@end

そして、viewDidLoadで初期化します。

_dynamicProperties = [[UIDynamicItemBehavior alloc] initWithItems:@[self.firstBox]];
_dynamicProperties.density = 1;      // 物体の密度
_dynamicProperties.elasticity = 0.5; // 物体の跳ね具合
_dynamicProperties.friction = 0.05;  // 表面の滑らかさ

[_animator addBehavior:_gravity];
[_animator addBehavior:_collision];
[_animator addBehavior:_dynamicProperties];

また、onViewTapped:forEvent:でオブジェクト追加時に_dynamicPropertiesにも追加するようにします。

[self.view addSubview:box];
[_gravity addItem:box];
[_collision addItem:box];
[_dynamicProperties addItem:box];

横から落ちたら消す

今のところ、横から落ちたら見えなくなりますが、オブジェクトを削除しているわけではないのでメモリの無駄です。

そこで、横から落ちたオブジェクトは削除するようにします。

まず、無名カテゴリをUICollisionBehaviorDelegateプロトコル準拠にします。

@interface UDSViewController () <UICollisionBehaviorDelegate>

そして、viewDidLoadcollisionDelegateと別のバウンダリを追加します。これは、横から落ちたらひっかかるように、@"bottom"よりもちょっと下で、幅は左右広めに取っています。

_collision.collisionDelegate = self;

[_collision addBoundaryWithIdentifier:@"outer-edge"
                            fromPoint:CGPointMake(-9999, h + 10)
                              toPoint:CGPointMake(9999+w, h + 10)];

そして、@"outer-edge"に降れたら消すように、次のコードを追加します。 collisionBehavior:beganContactForItem:はアイテムとボーダとの衝突時に呼ばれるメソッドです。

- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item
   withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p {
    if ([(NSString*) identifier isEqualToString:@"outer-edge"]) {
        [self removeItem:(UIView*)item];
    }
}

- (void)removeItem:(UIView*)item {
    NSLog(@"%@", item);
    [item removeFromSuperview];
    [_gravity removeItem:item];
    [_collision removeItem:item];
    [_dynamicProperties removeItem:item];
}

同じ色なら消す

衝突時にbackgroundColorが同じなら、アイテムを消すようにしてみます。

それにはまず、onViewTapped:forEvent:で色を適当につけます。

UIView* box = [UIView.alloc initWithFrame:CGRectMake(p.x, p.y, 48, 48)];
NSArray* colors = @[UIColor.grayColor, UIColor.redColor, UIColor.blueColor, UIColor.greenColor];
box.layer.backgroundColor = [colors[rand() % colors.count] CGColor];

そして、次のコードを追加します。collisionBehavior:beganContactForItem:withItem:atPoint:はアイテム同士の衝突時に呼ばれるメソッドです。

- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item1 withItem:(id<UIDynamicItem>)item2 atPoint:(CGPoint)p {
    CGColorRef color1 = [[(UIView*)item1 layer] backgroundColor];
    CGColorRef color2 = [[(UIView*)item2 layer] backgroundColor];
    if (CGColorEqualToColor(color1, color2)) {
        [self removeItem:(UIView*) item1];
        [self removeItem:(UIView*) item2];
    }
}

関連リンク

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。