iOSアプリでグラデーションを表現するときは、CAGradientLayerを使用することになると思いますが、そのグラデーションカラーをアニメーションで変化をつける方法のサンプルを記載します。
大きく分けて、CAGradientLayerの実装とアニメーションの実装、アニメーションのループの3パートにわけて記載します。
グラデーションを実装する
swiftでグラデーションを表現するには、CAGraidentLayerインスタンスを作成し、UIViewなどにinsertSublayerします。
fileprivate let gradientStartColor: UIColor = .init(hex: "007AB7")
fileprivate let gradientEndColor: UIColor = .init(hex: "C54F91")
fileprivate lazy var gradientLayer: CAGradientLayer = {
let gradient = CAGradientLayer()
gradient.frame = view.bounds
gradient.colors = [gradientStartColor, gradientEndColor]
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 1, y: 1)
gradient.drawsAsynchronously = true
return gradient
}()
view.layer.insertSublayer(gradientLayer, at: 0)
一例ですが、このようにCAGradientLayerを作成し、UIViewControllerのviewにinsertSublayerします。
これでグラデーションを表現することが出来ます。
グラデーションのアニメーションを実装する
swiftでアニメーションを実装する一番簡単な方法は、UIView.animateかと個人的には思いますが、こちらは出来ることが限られています。より詳細にアニメーションを実装したい場合は、CABasicAnimationを使う必要があります。
CAGradientLayerの何かしらのプロパティをアニメーションで変化させたいときはには、このCABasicAnimationが必要になります。
fileprivate let animationIdentifier: String = "colorChange"
fileprivate let animationTarget: String = "colors"
let anim = CABasicAnimation(keyPath: animationTarget)
anim.fromValue = [gradientStartColor.cgColor, gradientEndColor.cgColor]
anim.toValue = [gradientEndColor.cgColor, gradientStartColor.cgColor]
anim.duration = 2.0
anim.fillMode = .forwards
anim.isRemovedOnCompletion = false
gradientLayer.add(anim, forKey: animationIdentifier)
CABasicAnimationインスタンスをイニシャライズする際に、何をアニメーションするのかを指定します。今回は、グラデーションの色にアニメーションをつけたいので、"colors"と指定します。
次に、変化元と変化後の値を、fromValueとtoValueに指定します。
isRemovedOnCompletionは、アニメーション完了後に、アニメーション前の状態に戻すか、そのまま維持するか、を指定することが出来ます。
この状態で実行すると、1度だけアニメーションが実行されるようになります。
グラデーションのアニメーションを永続的に繰り返す
アニメーションを永続的に繰り返す最も簡単な方法は、repeatCount に .inifinityを指定することです。シンプルなアニメーションを実装するのであれば、これで事足りますが、今回はグラデーションカラーのアニメーションをスムーズに行いたかったので、別の方法を記載します。
// CABasicAnimationのインスタンスにdelegateを設定
anim.delegate = self
// delegateでアニメーション完了時の処理を追記
extension ViewController: CAAnimationDelegate {
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
print("Animation did stopp")
}
}
CABasicAnimationのインスタンスの delegateを活用して、animationの開始時・終了時に処理を記述することが出来ます。今回は、アニメーション終了時にアニメーションを再度実行する、という処理を追加することにより、アニメーションを永続的に実行するようにします。
その際に、何でも良いのですが、フラグを用いて、animationのfromValueとtoValueを都度入れ替え、グラデーションカラーの変化がスムーズになるように実装しています。
以下、サンプルの全コードです。
import UIKit
class ViewController: UIViewController {
fileprivate var animateFlag: Bool = true
fileprivate let animationIdentifier: String = "colorChange"
fileprivate let animationTarget: String = "colors"
fileprivate let gradientStartColor: UIColor = .init(hex: "007AB7")
fileprivate let gradientEndColor: UIColor = .init(hex: "C54F91")
fileprivate lazy var gradientLayer: CAGradientLayer = {
let gradient = CAGradientLayer()
gradient.frame = view.bounds
gradient.colors = [gradientStartColor, gradientEndColor]
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 1, y: 1)
gradient.drawsAsynchronously = true
return gradient
}()
override func viewDidLoad() {
super.viewDidLoad()
view.layer.insertSublayer(gradientLayer, at: 0)
animateGradient()
}
fileprivate func animateGradient() {
let anim = CABasicAnimation(keyPath: animationTarget)
if animateFlag {
anim.fromValue = [gradientStartColor.cgColor, gradientEndColor.cgColor]
anim.toValue = [gradientEndColor.cgColor, gradientStartColor.cgColor]
} else {
anim.fromValue = [gradientEndColor.cgColor, gradientStartColor.cgColor]
anim.toValue = [gradientStartColor.cgColor, gradientEndColor.cgColor]
}
anim.duration = 2.0
anim.fillMode = .forwards
anim.isRemovedOnCompletion = false
anim.delegate = self
gradientLayer.add(anim, forKey: animationIdentifier)
}
}
extension ViewController: CAAnimationDelegate {
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
animateFlag = !animateFlag
animateGradient()
}
}
extension UIColor {
convenience init(hex: String, alpha: CGFloat = 1.0) {
let v = Int("000000" + hex, radix: 16) ?? 0
let r = CGFloat(v / Int(powf(256, 2)) % 256) / 255
let g = CGFloat(v / Int(powf(256, 1)) % 256) / 255
let b = CGFloat(v / Int(powf(256, 0)) % 256) / 255
self.init(red: r, green: g, blue: b, alpha: min(max(alpha, 0), 1))
}
}
CAGradientLayerのアニメーションを実装する機会はこれまでなかったのですが、今回やってみて、意外と簡単に出来るものだなと思いました。
以上です。