Swift
CABasicAnimation

(初心者向けiOSアプリ開発)UIImageをアニメーション付きで回転させる話

More than 1 year has passed since last update.

やりたかったこと:
・前記事( http://qiita.com/CST_negi/items/7e75a4402f7ba9998270 )で温度を取得できたので、それをメータと針で表してみたい。
・それをアニメーション付きで描画したい。

おうちハックアプリをつくっていたわけですが、やっとおうちを監視する部分は完成しました!!

画像がこちら
スクリーンショット 2015-08-31 15.59.51.png

下半分のみ(実際はもっと滑らかに動きます)
2XvHBTjDDF.gif

機能としては、
・Webカメラからの画像を見られる
・時間がわかる
・室温がわかる
といった具合です。これを見て他のタブでエアコンつけたり天井のライトつけたりするわけですね。まだその部分は実装できてないのですが、超楽しみです。


では本題にうつります。

その前に注意点がひとつ
スクリーンショット 2015-08-31 17.04.52.png

このメータ部分は自作なのですが、針の部分に少し工夫をいれてて、「原点を針の丸部分にくるように下に空白をいれている」という工夫をしています。
なんでこんなことをしたかというと、これだと画像の赤丸の部分を中心にして回転するので想定した動きになるのでということです。

例えば原点を赤い針の画像の中心(真ん中)にすると、回転する際に
・アフィン変換で回転軸が赤丸部分にくるように上にずらす
・回転させる
・そのままだと上にずれたままなのでアフィン変換で下にずらすことによって位置を補正する
ということをしないといけません。
本来はこれができたらよかったんですが、どうしてもできなかったので画像の方を工夫しました。
こういうやり方があるよという話がありましたらコメントなどで教えて欲しいです。

(追記:
http://stackoverflow.com/questions/8275882/one-step-affine-transform-for-rotation-around-a-point
これでいいかもしれない。)


アニメーション付きで回転する方法は2種類あります。
①transform = CGAffineTransformMakeRotation(angleRad) をアニメーション処理にいれて使う
②CABasicAnimation(keyPath: "transform.rotation.z") を使う

それぞれ解説していきます。


①transform = CGAffineTransformMakeRotation(angleRad) をアニメーション処理にいれて使う

例えばこんな風に使います。
let angleRad:CGFloat = CGFloat((30 * M_PI) / 180.0)
self.ClockHandImage.transform = CGAffineTransformMakeRotation(angleRad)

ラジアンの角度を作った後それをアフィン変換による回転関数の引数に入れる手法です。画像の回転については、これだけで回転することができます。

(参考:https://sites.google.com/a/gclue.jp/swift-docs/ni-yinki100-ios/uikit/uiimageno-hua-xiangno-hui-zhuan-kuo-suo-fan-zhuan)

このアフィン変換を使った方法でアニメーションさせるのもそんなに苦ではありません。

ViewController.swift
func updateTempratureMeterValue(temperature : Double){
        var angle = -45 + 3 * temperature
        //-75 ~ 75の150度
        let angleRad:CGFloat = CGFloat((angle *  M_PI) / 180.0)

        UIView.animateWithDuration(1.0,
            // 遅延時間.
            delay: 0.0,
            // バネの弾性力. 小さいほど弾性力は大きくなる.
            usingSpringWithDamping: 0.5,
            // 初速度.
            initialSpringVelocity: 1,
            // 一定の速度.
            options: UIViewAnimationOptions.CurveLinear,
            animations: { () -> Void in
                // 回転用のアフィン行列を生成.
                self.ClockHandImage.transform = CGAffineTransformMakeRotation(angleRad)
            },
            completion: { (Bool) -> Void in
        })
    }

このようにUIView.animateWithDurationの処理に巻き込んであげましょう。
(参考:https://sites.google.com/a/gclue.jp/swift-docs/ni-yinki100-ios/uikit/animeshonwoendoresu-zai-shengsuru )

この処理はどういうことを行っているかというと、
・1秒後にアニメーション処理が完了するようにして
・遅延時間(処理が呼び出されてからアニメーションが始まるまでの時間)は無し(0)にして
・0.5の弾性力を持つ動きをし
・初速度を1で
・一定の速度で
・ClockHandImage(UIImage型)を指定された角度まで回転させる
ということをしています。


②CABasicAnimation(keyPath: "transform.rotation.z") を使う

次にCABasicAnimationです。
CAっていうのは( https://ja.wikipedia.org/wiki/Core_Animation )にある通り、CoreAnimationの略みたいですね。はMac OS Xを構成するミドルウェアの一種で、ユーザインタフェース表現用のアニメーション処理をサポートするそうです。

このkeypathがググっても簡単に出てこないところが驚きなんですけど
・transform.translationなら移動
・transform.rotationなら回転
・transform.scaleなら拡大縮小
を表すようですね。
(参考:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/Key-ValueCodingExtensions/Key-ValueCodingExtensions.html#//apple_ref/doc/uid/TP40004514-CH12-SW8 )

今回はtransform.rotationを使います。
z軸は画面に対して垂直な軸を表しますので、その軸を使った回転方法としましては、回ってる独楽を上から見る感じと同じだと思ってくれればよいですね。

ViewController.swift
    func rotateGear(key : String){

        let anim = CABasicAnimation(keyPath: "transform.rotation.z")
        anim.fromValue = 0
        anim.toValue = 1 * M_PI
        anim.duration = 2.0
        anim.repeatCount = HUGE
        GearImage1.layer.addAnimation(anim, forKey: key)
        GearImage3.layer.addAnimation(anim, forKey: key)

        let animInv = CABasicAnimation(keyPath: "transform.rotation.z")
        animInv.fromValue = 0
        animInv.toValue = -1 * M_PI
        animInv.duration = 2.0
        animInv.repeatCount = HUGE

        GearImage2.layer.addAnimation(animInv, forKey: key+"Inv")
    }

このコードを元に解説していきます。(GearImageというのは最初のモニターのスクショ画像で言う所の、真ん中の歯車でこの処理により永遠に回ってます。)

まず
let anim = CABasicAnimation(keyPath: "transform.rotation.z")
で「このアニメーションはZ軸に対する回転」であるという指定をします。

次に
anim.fromValue = 0
anim.toValue = 1 * M_PI

によって、「0からπまでの回転角度を用いる」という指定をします。

そして
anim.duration = 2.0
によって、「アニメーションは2秒間行われる」ということを指定します。

また
anim.repeatCount = HUGE
によって「何回アニメーションを繰り返すか」を設定します。
※HUGEと指定すると無限に繰り返されます。

最後に
GearImage1.layer.addAnimation(anim, forKey: key)
画像にアニメーションを付け加える処理をしておしまいです。

(forKey:引数については、CAAnimation終了時にアニメーションを区別して追加処理を行う際に必要なので、もしそういうことがしたければきちんと名前をつけたほうがよさそうですね)
(参考: http://captainshadow.hatenablog.com/entry/20110222/1298352073 )

以上です。何か指摘がありましたらコメント欄のほうまでよろしくお願いします。