UIKit Dynamicsを使う簡単なチュートリアルを書いてみました。
箱に重力を追加する
まずは箱をひとつ作って、それを重力をつけて下に落としてみます。
Xcodeで「Single View Application」なプロジェクトを新規作成します。
灰色で、64x64の大きさのUIViewの箱を上のほうにつくってOutletに接続します。
続いて、重力を付けるように実装します。
UDSViewController.mにあるビューコントローラの無名カテゴリにanimatorとgravityを追加します。
@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>
そして、viewDidLoadでcollisionDelegateと別のバウンダリを追加します。これは、横から落ちたらひっかかるように、@"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 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。