##はじめに
この記事は、ZOZOテクノロジーズ #4AdventCalendar2019の記事です。
昨日は@kurararararaさんのCharles for Macでレスポンスを変更する方法の記事でした。
ZOZOテクノロジーズでは、他にもAdventCalenderを書いている方がいらっしゃるので、よかったらみていってください。
- ZOZOテクノロジーズ #1AdventCalendar2019
- ZOZOテクノロジーズ #2AdventCalendar2019
- ZOZOテクノロジーズ #3AdventCalendar2019
- ZOZOテクノロジーズ #5AdventCalendar2019
##映えを狙う
今回私@koiwai2020は、WEARに投稿したこちらのコーディネート画像に雪を降らせて、映えを狙いたいと考えました。
今回はCABasicAnimationを使ったアニメーションによって、雪が降らせてみることに挑戦しました。
早速ですがアニメーションを使用して、投稿画像に雪を降らせた完成形がこちらになります。
— 小岩井 (@WfODXAd0jmop1Ev) December 16, 2019
CABasicAnimationにはopacity
、backgroundColor
、position
、transform.scale.x
などのkey pathが用意されていて、これを指定してアニメーションを作ります。
今回雪が降る様子を作る為に、以下の2つの動きをCABasicAnimationを使って表現しました。
- 雪の画像を移動させる。
- 雪の画像を徐々に薄くする。
##画像を移動させるアニメーション
雪を降らせるにあたって、まず画像を移動させるアニメーションを作ります。
移動させるアニメーションは、position
をkey pathに設定して使います。
func fallAnimation() -> CABasicAnimation {
let fallAnimation = CABasicAnimation(keyPath: "position")
// 0から1までのランダムな係数を用意して、アニメーションの位置を調整するための値
let random = CGFloat.random(in: 0 ..< 1)
// アニメーション開始時の座標
let fromPoint = CGPoint(x: UIScreen.main.bounds.width * random, y: 0)
// アニメーション完了時の座標
let toPoint = CGPoint(x: UIScreen.main.bounds.width * random, y: UIScreen.main.bounds.height)
// アニメーション開始時の座標を設定
fallAnimation.fromValue = fromPoint
// アニメーション完了時の座標を設定
fallAnimation.toValue = toPoint
// アニメーションの速度を設定
fallAnimation.duration = 1.5
return fallAnimation
}
##画像を透過させるアニメーション
次に降る雪が地上に近くにつれて、溶けていく様子を画像を透過させるアニメーションを使って作ります。
透過のアニメーションは、opacity
をkey pathに設定して使います。
func thawAnimation() -> CABasicAnimation {
let thawAnimation = CABasicAnimation(keyPath: "opacity")
// アニメーション開始時のalpha値
thawAnimation.fromValue = 1.0
// アニメーション終了時のalpha値
thawAnimation.toValue = 0.0
// アニメーションの速度を設定
thawAnimation.duration = 1.5
// アニメーション完了時の状態を保持
thawAnimation.fillMode = .forwards
return thawAnimation
}
##作ったアニメーションを画像に反映させる
作ったアニメーションを実際に、画像に反映させてみます。
アニメーションは、CALayerのadd(_:forKey:)
を呼び出したタイミングで開始されます。
その為、次のstartSnowAnimation
が呼び出されたタイミングで雪の画像が徐々に薄くなりながら、落ちていきます。
func startSnowAnimation() {
view.addSubview(snowImageView)
snowImageView.layer.add(fallAnimation(), forKey: "")
snowImageView.layer.add(thawAnimation(), forKey: "")
}
##アニメーションを繰り返す
実際にstartAnimation
を実行してみると、一粒の雪しか降ってきませんでした。
なので今回は、Timerを使用して一定の周期でアニメーションを開始するようにしました。
let snowAnimationTimer = Timer.scheduledTimer(timeInterval: 0.2,
target: self,
selector: #selector(startSnowAnimation),
userInfo: nil,
repeats: true)
用意したTimerを、アニメーションを開始したい任意のタイミングで動かします。
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
RunLoop.main.add(snowAnimationTimer, forMode: .common)
}
これで雪のアニメーションが繰り返し行われるので、自然な見た目になります。
##アニメーションが終ったら
アニメーションが終ったら、追加したアニメーションや画像の削除を行います。
アニメーションの完了には、CAAnimationDelegateにて用意されているanimationDidStop:finished:
を使います。
animationDidStop:finished:
を呼ぶ為には、最初に作ったアニメーションの設定処理に以下の設定を追加する必要があります。
- アニメーションにdelegateを設定する。
-
isRemovedOnCompletion
プロパティを無効にする。
isRemovedOnCompletionは、アニメーションが完了した際に削除するかどうかを決定します。
これが有効のままですと、animationDidStop:finished:
は呼ばれずに、アニメーションのオブジェクトは削除されます。
透過させるアニメーションthawAnimation
を例に設定を追加すると、次のようになります。
func thawAnimation() -> CABasicAnimation {
let thawAnimation = CABasicAnimation(keyPath: "opacity")
thawAnimation.delegate = self // CAAnimationDelegateの設定を追加
thawAnimation.fromValue = 1.0
thawAnimation.toValue = 0.0
thawAnimation.duration = 1.5
thawAnimation.fillMode = .forwards
thawAnimation.isRemovedOnCompletion = false //isRemovedOnCompletionはデフォルトtrue
return thawAnimation
}
設定が済んだら、animationDidStop:finished:
内にアニメーション完了時の処理を追加します。
今回は、アニメーションが完了したタイミングで雪画像のImageViewごと削除しています。
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
snowImageView.removeFromSuperview()
}
以上で、投稿したコーディネート画像を映えさせる?雪のアニメーションの実装ができました。
##さいごに
CABasicAnimationのfromValue
やtoValue
、duration
を調整することでアニメーションの印象が変わりました。
普段慣れ親しんでいる雪であっても、実際に表現するとなると難しいのだなと感じました。
明日は、@Kyou13さんです。
読んで下さった皆さま、ありがとうございました🙇♂️🏂🙇♂️