16/11/12 修正
iOS10から既存のanimationDidStopは廃止になりました。
今まではNSObjectのメソッドでしたが、今後はCAAnimationのメソッドになります。
よってanimationDidStopの呼び出しも変更になります。
class ViewController: UIViewController, CAAnimationDelegate{ //<- CAAnimationのDelegateが追加が必要
今まではDelegateの指定が要らなかったのですが、今度から必要です。
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
}
overrideの指定も不要です。
以上、変更点でした。
16/03/14 タイトル及び本文修正
CABasicAnimationで変更した結果のままにしたい場合は以下のようにします。
let animateStrokeEnd = CABasicAnimation(keyPath: "progress")
animateStrokeEnd.delegate = self // animationDidStopを呼び出すのに必須
animateStrokeEnd.fromValue = fromValue
animateStrokeEnd.toValue = toValue
animateStrokeEnd.removedOnCompletion = false
animateStrokeEnd.fillMode = kCAFillModeForwards
_sublayer?.addAnimation(animateStrokeEnd, forKey: "progress")
しかし、この状態はアニメーションの処理がずっと動きっぱなしの状態でしかも実際にはCALayerの値を変更してるわけではないようです。
なので、アニメーションを止める且つCALayerの値を実際に変更してやる必要があるけど、animationDidStopで実行してやると丁度良いのではないかと。
override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
_sublayer?.propery = toValue
_sublayer?.removeAnimationForKey("progress") //こちらか下か
_sublayer?.removeAllAnimations() // どちらか都合の良い方を使う
}
以上です。書きたかった事をまとめればえらくシンプルになりました。
下記は修正前15/10/10時点の記事。備忘録でとりあえず残しておきます。
ちょっとまだ整理仕切れてないけど。
CABasicAnimationとUIBezierPathを使ったアニメーションを試していたところ、CABasicAnimationのアニメーションとはCALayerのdrawInContextをプロパティのfromValueからtoValueまで再描画を繰り返す動きをしている事だと分かった。
(CALayerのサブクラスでdrawInContextでログを出すようにでもして貰ったら分かります。)
ところで、CABasicAnimationはアニメーションが終了したら、描画した内容がクリアされる問題があって、それを解決するにはプロパティのremovedOnCompletionとfillModeをセットで設定してやる。こんな形で。
let animateStrokeEnd = CABasicAnimation(keyPath: "progress")
animateStrokeEnd.fromValue = fromValue
animateStrokeEnd.toValue = toValue
animateStrokeEnd.removedOnCompletion = false
animateStrokeEnd.fillMode = kCAFillModeForwards
ところがプロパティのremovedOnCompletionを使うとtoValueまでの値になっても再描画の動きが止まらない事が判明。removedOnCompletionという単語の意味を考えればそうなってもおかしくないのだが、最近まで全然気付いてなかった・・・。
なんにせよ、removedOnCompletionを使うとずっとアニメーションそのものを削るかそのアニメーションをしているCALayerそのものを削除しない限り、処理が動きっぱなしだと言う事。それはあまり良くないので、アニメーションを削るか、CALayerを削除する処理をしないならremovedOnCompletionは使わない方がよい。
その代わりの処理としては以下のような方法がいいみたいです。
/* 別のファイルもしくは上記でサブレイヤークラスを作成 */
var _subLayer : SubLayer? = nil
/* 省略 */
func moveLayer {
let animateStrokeEnd = CABasicAnimation(keyPath: "progress")
animateStrokeEnd.fromValue = fromValue
animateStrokeEnd.toValue = toValue
//animateStrokeEnd.removedOnCompletion = false コメントアウト
animateStrokeEnd.fillMode = kCAFillModeForwards
animateStrokeEnd.delegate = self //デリゲートを設定
_sublayer?.addAnimation(animateStrokeEnd, forKey: "progress")
}
override func animationDidStop(anim: CAAnimation!, finished flag: Bool) {
let pinnedProgressNumber : CGFloat = anim.valueForKey("toValue") as CGFloat
_sublayer?.toValue = pinnedProgressNumber
}
CABasicAnimationのデリゲートをセットし、アニメーションの終了時に呼び出されるメソッドをオーバーライド。その中で、アニメーション後の値をセットしてやる。例えば移動のアニメーションなら移動先の座標など。
以上です。