2014/01/18

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

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

箱に重力を追加する

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

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

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

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

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

1
2
3
4
@interface UDSViewController ()
@property UIDynamicAnimator* animator;
@property UIGravityBehavior* gravity;
@end

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

1
2
3
4
5
6
7
8
9
- (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を追加します。

1
2
3
4
5
@interface UDSViewController ()
@property UIDynamicAnimator* animator;
@property UIGravityBehavior* gravity;
@property UICollisionBehavior* collision;
@end

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

1
2
3
4
5
6
7
8
9
10
11
- (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の下に次のコードを追加します。

1
2
3
4
5
6
7
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にします。

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

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

1
2
3
4
5
6
7
8
9
10
11
- (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を追加します。

1
2
3
4
5
6
@interface UDSViewController ()
@property UIDynamicAnimator* animator;
@property UIGravityBehavior* gravity;
@property UICollisionBehavior* collision;
@property UIDynamicItemBehavior* dynamicProperties;
@end

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

1
2
3
4
5
6
7
8
_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にも追加するようにします。

1
2
3
4
[self.view addSubview:box];
[_gravity addItem:box];
[_collision addItem:box];
[_dynamicProperties addItem:box];

横から落ちたら消す

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

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

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

1
@interface UDSViewController () <UICollisionBehaviorDelegate>

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

1
2
3
4
5
_collision.collisionDelegate = self;
 
[_collision addBoundaryWithIdentifier:@"outer-edge"
                            fromPoint:CGPointMake(-9999, h + 10)
                              toPoint:CGPointMake(9999+w, h + 10)];

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (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:で色を適当につけます。

1
2
3
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:はアイテム同士の衝突時に呼ばれるメソッドです。

1
2
3
4
5
6
7
8
- (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 件のコメント:

コメントを投稿

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