Swiftで遊ぼう! on Hatena

あしたさぬきblogでやってた初心者オヤジのiOSプログラミング奮闘記がHatenaに来ました

Swiftで遊ぼう! - 417 - UIDynamicAnimator再び 1

Swiftで遊ぼう!の古い記事-> Life-LOG OtherSide

遠い昔、簡単なチュートリアルをクリアした後に無謀にもUIDynamicsの勉強をしようとしていました。

Swiftで遊ぼう! - 124 - UIKit:Life-LOG OtherSide
Swiftで遊ぼう! - 125 - UIKit UIDynamicAnimator:Life-LOG OtherSide
Swiftで遊ぼう! - 126 - UIKit UIDynamicAnimator/gravity:Life-LOG OtherSide
Swiftで遊ぼう! - 127 - UIKit UIDynamicAnimator/gravity2:Life-LOG OtherSide
Swiftで遊ぼう! - 128 - UIKit UIDynamicAnimator/collision:Life-LOG OtherSide
Swiftで遊ぼう! - 129 - UIDynamicAnimator/collisionインタラクション無し:Life-LOG OtherSide
Swiftで遊ぼう! - 130 - UIDynamicAnimator/collisionインタラクション:Life-LOG OtherSide
Swiftで遊ぼう! - 131 - UIDynamicAnimator/collisionインタラクション色々:Life-LOG OtherSide
Swiftで遊ぼう! - 132 - UIDynamicAnimator/重力から解放されるために:Life-LOG OtherSide
Swiftで遊ぼう! - 137 - UIDynamicAnimator/重力からの解放に向かって:Life-LOG OtherSide
Swiftで遊ぼう! - 138 - UIDynamicAnimator/重力に...(キャスティング):Life-LOG OtherSide

今ならもう少し理解できるかもしれないと考え、中途半端に終わっていたUIDynamicsのチュートリアルに取り組みます。

元ネタは次のサイトです。素晴らしいチュートリアルが数多くあるので初心者には興味深いですね。

www.raywenderlich.com

さっさと取りかかりましょう。

新しいプロジェクト名「DynamicDemo」にして「Single View Application」を用意します。

もう何のためらいもありません。デフォルトで「view」が存在するので、この「view」に青い四角を加えます。

普通ならレイアウトを考えてオブジェクトを配置しなければならないのですが、今日から勉強する内容にレイアウトは要らないのでiPhone5sのポートレイトで固定して考えます(iPhone5sを実機テストに使用してる理由もあるからです)。

let square = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))

これで四角い領域を用意しました。これでsquareというオブジェクトが作られました。

そしてそれの属性(プロパティ)のバックグラウンドカラーを変えます。

square.backgroundColor = UIColor.blueColor()

UIColor.blueColor()だって、あの頃はよく分かってなかったです。クラスメソッドが理解できていなかったからです。

これだけでも画面にスクエアは表示されません。最終的に既存のviewに加える必要があります。

override func viewDidLoad() {
 super.viewDidLoad()
        
 let square = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
 square.backgroundColor = UIColor.blueColor()
 view.addSubview(square)
}

このコードの位置ですが、これも今や抵抗なくviewDidLoad()メソッド内に書く意味は分かります。あの頃は、これさえもなかなか受け入れることができなかったです。

早速ラン(Cmd + R)すると次のように描画されます。

f:id:yataiblue:20150817165752j:plain

次に動きのないUIViewクラスに物理法則を与えます。

これがUIDynamicAnimatorクラスです。UIDynamicItemプロトコールを実装したオブジェクトなら何でもUIDynamicAnimatorの作用を加えることができるということ。このプロトコールは「UIViewクラスとUICollectionViewLayoutAttributesクラス」に実装されています。

ということはUIViewクラスは、そのまま「UIDynamicItem」として使えると言うことです。

ではこの物理法則が詰まったクラスをプロパティとして持たせましょう。

ViewControllerクラスのプロパティとして次のコードを入れます。

var animator: UIDynamicAnimator!

ここで「!」が出てきました。あの頃は、「?」でなくて、どうして「!」なのか? なんて事で悩んでいました。実際のところ「!」でも「?」でも構わないんです。ポイントは別の所にあります。このプロパティがオプショナル型って所が重要なんです。

なぜオプショナルが望ましいのか? 実はクラスにプロパティを加える時、できるだけイニシャライザを変更しない方が望ましいという決まり事があるからです。

「?」の場合、後で必ずアンラップしないとランタイムエラーが生じるので、必ず値を入れることを条件にすれば、「!」を使用する方がアンラップを気にしなくていいため使いやすいと言うことです。

そして物理的な動きも作ります。

var gravity: UIGravityBehavior!

この動きをUIDynamicItemに加えると重力が生まれるんです!

まずviewの空間に物理的な作用がおよぶようにするためにUIDynamicAnimatorにリファレンスビューを組み込みます。

animator = UIDynamicAnimator(referenceView: view)

次にviewの中にあるsquareに重力を与えます。

gravity = UIGravityBehavior(items: [square])

そして物理作用が及ぶ新しい空間animatorにgravityを加えます。これはviewにsubviewを加える感覚と似ています。

class ViewController: UIViewController {
    
    var animator: UIDynamicAnimator!
    var gravity: UIGravityBehavior!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let square = UIView(frame: CGRect(x: 100, y: 100,
                                  width: 100, height: 100))
        square.backgroundColor = UIColor.blueColor()
        view.addSubview(square)
        
        animator = UIDynamicAnimator(referenceView: view)
        gravity = UIGravityBehavior(items: [square])
        animator.addBehavior(gravity)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

これで準備ができました。squareに重力を与えます。ちょっと分かりにくいネーミングになっていますが、変数gravityが重力の組み込まれたsquareです。ラン(Cmd +R)してみると、ビックリ(以前同じ事をしているから驚きはないですね)、青い四角が下へ落ちて画面から消えてしまいました。

次は画面から消えてしまわないように境界線を作ります。

境界線をどこに設定するのか?

まず重力の動きだけオブジェクトに加えても画面内にオブジェクトを留めることはできません。オブジェクトを画面内に留めるために境界線を作る必要があります。境界線を設定して底に衝突するという仕組みなので、衝突動作をオブジェクトに加える必要があります。それがUICollisionBehaviorクラスです。

ということで重力と同様にコリージョン(衝突)オブジェクトを作ります。

プロパティに次のコードを入力します。

var collision: UICollisionBehavior!

次にviewDidLoad()メソッド内に次のコードを入れます。

collision = UICollisionBehavior(items: [square])
collision.translatesReferenceBoundsIntoBoundary = true
animator.addBehavior(collision)

これで重力と衝突の作用がsquareオブジェクトに加わりました。

そして、そのプロパティ「translatesReferenceBoundsIntoBoundary」がリファレンスViewとの境界線に衝突するかどうかの判断をさせることになります。

たったこれだけでsquareにリファレンスviewとの境界で衝突ガ生じます(衝突ということはそこから先に動かないということです)。

これでラン(Cmd + R)させると青い四角は下でバウンズして止まります。当に重力があるようです。

次は障害物を作って見ます。

障害物は、squareと同じ要領でviewDidLoad()メソッドの加えます。

let barrier = UIView(frame: CGRect(x: 0, y: 300, width: 130, height: 20))
barrier.backgroundColor = UIColor.redColor()
view.addSubview(barrier)

このままラン(Cmd + R)すると、青い四角形は赤い障害物が存在しないかのようにすり抜けていきます。

f:id:yataiblue:20150818085355j:plain

これも当然と言えば当然です。このbariierはsquareと関わりが無いからです。重力環境は無視してsquareと衝突関係を作るためにcollisionの中にbarrierを加えればいいんです。

collision = UICollisionBehavior(items: [square, barrier])

こうすれば重力関係は無視されますが、無重力空間でsquareにぶつけられた衝撃で赤いbarrierは飛ばされて左の上の方に固定されます。なんか不自然な動きです。

f:id:yataiblue:20150818100546j:plain

次に、このcollisionの世界に境界線を設定することができます。それが「addBoundaryWithIdentifier()」メソッドです。Identifierで名前を付けて、UIBezierPathで形を作ります。このUIBezierPathも以前は悩んでいましたが、今はなんとなくりかいできます(^^;)。色々な形を与えることができるんですが、パラメーターで「rect」も取ることができます。ここでbarrier.frameを与えます。

collision.addBoundaryWithIdentifier("block", forPath: UIBezierPath(rect: barrier.frame))

これでbarrierは動かなくなりsquareは衝突すると向きを変えて下に落ちていきます。

f:id:yataiblue:20150818102901j:plain

ここまで無理なく理解できます。

以前はここからデリゲーションとプロトコールの話が出てきて頓挫してしまいました。

しかし、今の私は苦も無く理解できていると言えるでしょう。

明日から後半に入ります。