Swiftで遊ぼう! - 418 - UIDynamicAnimator再び 2
Swiftで遊ぼう!の古い記事-> Life-LOG OtherSide
下記のチュートリアルに取り組んでいます。
UICollisionBehaviorクラスは、オブジェクト同士の衝突で生じる物理効果を表現するジェネリックな存在です。具体的な動きをItem(UIViewオブジェクトの事ですが、DynamicAnimatorの空間ではItemとして扱う)に加えるために、このクラスはデリゲーションメソッドを用意しています。それがUICollisionBehaviorDelegateプロトコールです。
デリゲーションとプロトコールでかなり悩んでいました。
しかし今は理解しているので、次の記事を読んで下さい。
クラスの宣言部分で準拠させたいプロトコールを加えます。
class ViewController: UIViewController, UICollisionBehaviorDelegate {
次にUICollisionBehaviorクラスのプロパティにViewController自身をプロトコールの型として持た*1せるために、次のコードをviewDidLoad()メソッドに入れます。
collision.collisionDelegate = self
これでデリゲーションメソッドの実装の準備が整ったので必須メソッドをコーディングするのですが、なぜかUICollisionBehaviorDelegateに必須メソッドはありません。すべてオプショナルメソッド扱いです。
次のオプショナルメソッドは、オブジェクトの衝突が生じた時に呼ばれる関数で、何もコーディングされていなくても必ず「衝突(collision)」が生じた時に呼ばれています。
func collisionBehavior(behavior: UICollisionBehavior, beganContactForItem item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, atPoint p: CGPoint) { }
衝突が生じた時にコンソールにメッセージを書き出す機能を実装をします。
func collisionBehavior(behavior: UICollisionBehavior, beganContactForItem item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, atPoint p: CGPoint) { print("Boundary contact occurred - \(identifier)") }
こうすることで、collisionに組み込んだ障害物に衝突した時とリファレンスビューの境界部分に当たった時にこのメソッドが呼ばれるはずです。ラン(Cmd + R)させるとコンソールに次のようなメッセージが出てきました。
このリファレンスビューに設定している境界線は赤い障害物と画面周囲で、赤い障害物にはIdentifierが設定されていますが、画面周囲にはIdentifierが設定されていないので、最初の2回は"block"を返せますが、後はnilになります。このタイプがNSCopyingのオプショナルになっていますね。NSCopyingというのが初耳でした。
これがどういうものか確認する必要があるます。まだまだ理解できていないことが山のようにあります。
NSCopyingはプロトコールです。持っているメソッドは1つだけで「codeWithZone」だそうです。これをパラメーターとして持っているということはUICollisionBehaviorクラスは既に準拠していると言うことでしょう。
どちらにしろsquareが障害物に当たった時にメッセージが流れるのも少し感動的かもしれませんが、視覚的に色が変わるともっと感動的です。
このメソッドで扱えるオブジェクトは、「item」ですが、DynamicItemクラスになります。collisionで設定されているDynamicItemは「square」なので、メソッド内でキャストして使います。これはダウンキャストではなく単なるタイプキャストです。
let collidingItem = item as! UIView
そしてぶつかった時に色を付けます。
collidingItem.backgroundColor = UIColor.yellowColor()
次はアニメーションを扱うクラスメソッド。これも初出なんですが、アニメーションはまた改めて勉強するので軽く流します。ここでしているのは0.3秒かけて元の青色に戻すという作業です。
UIView.animateWithDuration(0.3) {
collidingView.backgroundColor = UIColor.blueColor()
}
ここまでのデリゲーションメソッドは衝突をモニターリングするメソッドで、衝突時の物理作用のパラメーターを変更することはできません。この物理作用のパラメーターを変えるクラスはUIDynamicItemBehaviorです。
まずこのクラスを実装させるためプロパティを作ります。
let itemBehavior = UIDynamicItemBehavior(items: [square])
これでsquareの物理世界を変更することができます。変更できる項目には以下のようなものがあります。
- elasticity
- friction
- resistance
- angularResistance
- allowsRotation
説明はパスしますが、チュートリアルで「elasticity」を変更しています。
itemBehavior.elasticity = 1.0
elasticityが「1.0」というのは弾性100%で減速無しの跳ね返りが生じます。
この変化をanimatorという空間に反映させる必要があります。
animator.addBehavior(itemBehaviour)
ラン(Cmd + R)してみると、squareが弾み続けるでしょう。注意すべき事はanimatorというUIDynamicAnimatorという物理空間に組み込まないと動きが加わりません。
もう少しダイナミックな動きを加えるために、最初の衝突が生じた時に2つ目のsquareを発生させて、最初のsquareと引っ付いた(実際には見えませんが)状態で同調して動かしてみます。
プロパティにコンタクトを判定する変数を作ります。
var firstContact = false
collision behaviorデリゲートメソッドの中にファーストコンタクトを判断して、新しいDynamicItemを生み出します。
最初のコンタクトを判断するのが次のメソッドだったのでこのメソッドを拡張します。
func collisionBehavior(behavior: UICollisionBehavior, beganContactForItem item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, atPoint p: CGPoint) { print("Boundary contact occurred - \(identifier)") print("Boundary contact occurred - \(identifier)") let collidingItem = item as! UIView collidingItem.backgroundColor = UIColor.yellowColor() UIView.animateWithDuration(0.3) { collidingItem.backgroundColor = UIColor.blueColor() } if (!firstContact) { firstContact = true let secondSquare = UIView(frame: CGRect(x: 30, y: 0, width: 100, height: 100)) secondSquare.backgroundColor = UIColor.grayColor() view.addSubview(secondSquare) collision.addItem(secondSquare) gravity.addItem(secondSquare) let attach = UIAttachmentBehavior(item: collidingItem, attachedToItem: secondSquare) animator.addBehavior(attach) } }
ここでラン(Cmd + R)すると、最初の青い四角形が落ちて、障害物と衝突したときに灰色の四角形が現れて一定の距離を保ちながら同調して落ちていきます。
なかなか感動的でした。
今日はここまで。