[Swift5]Animation用の自作Viewクラス


1.はじめに

アニメーションっていちいち打つものが多くて面倒だな。

そうだ!クラスを作っちゃおう!


2.コード

というわけで今回のコードはこんな感じです。


AnimationView

class AnimationView:UIView,CAAnimationDelegate{

var didStopMethodList : [((AnimationView)->())] = []
var didStartMethodList : [((AnimationView)->())] = []

func animateScaleChange(duration:Double,toValue:CGFloat,fromValue:CGFloat = 1){
let animation = CABasicAnimation(keyPath: "transform.scale")
animation.fromValue = fromValue
animation.toValue = toValue
animation.duration = duration
animation.fillMode = CAMediaTimingFillMode.forwards
animation.isRemovedOnCompletion = false
animation.delegate = self
self.layer.add(animation, forKey: nil)
}

func animateOpacityChange(duration:Double,toValue:CGFloat,fromValue:CGFloat = 1){
let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = fromValue
animation.toValue = toValue
animation.duration = duration
animation.fillMode = CAMediaTimingFillMode.forwards
animation.isRemovedOnCompletion = false
animation.delegate = self
self.layer.add(animation, forKey: nil)
}

func animateMoveX(duration:Double,toValue:CGFloat,fromValue:CGFloat){
let animation = CABasicAnimation(keyPath: "position.x")
animation.fromValue = fromValue
animation.toValue = toValue
animation.duration = duration
animation.fillMode = CAMediaTimingFillMode.forwards
animation.isRemovedOnCompletion = false
animation.delegate = self
self.layer.add(animation, forKey: nil)
}

func animateMoveX(duration:Double,distance:CGFloat){
let animation = CABasicAnimation(keyPath: "position.x")
animation.fromValue = self.layer.anchorPoint.x
animation.toValue = self.layer.anchorPoint.x + distance
animation.duration = duration
animation.fillMode = CAMediaTimingFillMode.forwards
animation.isRemovedOnCompletion = false
animation.delegate = self
self.layer.add(animation, forKey: nil)
}

func animateMoveY(duration:Double,toValue:CGFloat,fromValue:CGFloat){
let animation = CABasicAnimation(keyPath: "position.y")
animation.fromValue = fromValue
animation.toValue = toValue
animation.duration = duration
animation.fillMode = CAMediaTimingFillMode.forwards
animation.isRemovedOnCompletion = false
animation.delegate = self
self.layer.add(animation, forKey: nil)
}

func animateMoveY(duration:Double,distance:CGFloat){
let animation = CABasicAnimation(keyPath: "position.y")
animation.fromValue = self.layer.anchorPoint.y
animation.toValue = self.layer.anchorPoint.y + distance
animation.duration = duration
animation.fillMode = CAMediaTimingFillMode.forwards
animation.isRemovedOnCompletion = false
animation.delegate = self
self.layer.add(animation, forKey: nil)
}

func animateMove(duration:Double,toValue:CGPoint,fromValue:CGPoint){
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = fromValue
animation.toValue = toValue
animation.duration = duration
animation.fillMode = CAMediaTimingFillMode.forwards
animation.isRemovedOnCompletion = false
animation.delegate = self
self.layer.add(animation, forKey: nil)
}

func animateMove(duration:Double,distance:CGPoint){
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = self.layer.anchorPoint
animation.toValue = CGPoint(x: self.layer.anchorPoint.x + distance.x, y: self.layer.anchorPoint.y + distance.y)
animation.duration = duration
animation.fillMode = CAMediaTimingFillMode.forwards
animation.isRemovedOnCompletion = false
animation.delegate = self
self.layer.add(animation, forKey: nil)
}

func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
for method in didStopMethodList{
method(self)
}
}

func animationDidStart(_ anim: CAAnimation){
for method in didStartMethodList{
method(self)
}
}
}



3.関数の説明

関数

説明

animateScaleChange(duration:,toValue:,fromValue:)
-
大きさをアニメーションします。durationに処理時間、toValueにアニメーション終了時の大きさ(元の大きさを1とする)、fromValueにアニメーション開始時の大きさ(元の大きさを1とする)を入力します。fromValueが未設定の場合はfromValue = 1となります。

animateOpacityChange(duration:,toValue:,fromValue:)
-
不透明度をアニメーションします。durationに処理時間、toValueにアニメーション終了時の不透明度、fromValueにアニメーション開始時の不透明度を入力します。fromValueが未設定の場合はfromValue = 1となります。

animateMoveX(duration:,toValue:,fromValue:)
-
duration(秒)かけて、fromValueに設定したx座標から、toValueに設定したx座標まで移動します。

animateMoveX(duration:,distance:)
-
duration(秒)かけて、元のx座標から、distanceに設定したx座標分移動します。

animateMoveY(duration:,toValue:,fromValue:)
-
duration(秒)かけて、fromValueに設定したy座標から、toValueに設定したy座標まで移動します。

animateMoveY(duration:,distance:)
-
duration(秒)かけて、元のy座標から、distanceに設定したy座標分移動します。

animateMove(duration:,toValue:,fromValue:)
-
duration(秒)かけて、fromValueに設定したCGPointから、toValueに設定したCGPointまで移動します。

animateMove(duration:,distance:)
-
duration(秒)かけて、元のCGPointから、distanceに設定したCGPointのx座標およびy座標分移動します。


4.Delegateに個別のメソッドを設定

AnimationのDelegateに書くコードを、Viewインスタンスごとに設定するのが面倒くさい!と思い、インスタンスごとにメソッドを設定できるようにしてみました。

それが以下の部分


AnimationView

    var didStopMethodList : [((AnimationView)->())] = []

var didStartMethodList : [((AnimationView)->())] = []

...

func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
for method in didStopMethodList{
method(self)
}
}

func animationDidStart(_ anim: CAAnimation){
for method in didStartMethodList{
method(self)
}
}


didStopMethodList、didStartMethodListとして関数型の配列を作りました。

animationDidStopやanimationDidStartで、それぞれの配列に登録されたメソッドが順番に実行されます。


5.使用例

使用例は以下の通り。

import UIKit

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

var a = AnimationView()
a.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
a.backgroundColor = UIColor.black
self.view.addSubview(a)
a.didStopMethodList.append(printB)
a.didStopMethodList.append(printA)
a.animateMove(duration: 1, distance: view.center)

}

func printA(view:AnimationView){
print("a")
}

func printB(view:AnimationView){
print("b")
}

//プリント結果
//b
//a

}

class AnimationView:UIView,CAAnimationDelegate{
...
}

四角いViewが真ん中に移動し、アニメーション終了時にb、aとプリントされます。printA、printBメソッドの定義でわかるように、引数はAnimationView型のみにしています。もし、個別の引数を設定したい場合は、AnimationViewにプロパティを追加し、追加するメソッドの中でそのプロパティを参照、変更する形でご利用ください。

(もし配列に異なる引数の関数を同時に入れることができたら教えていただけると嬉しいです)


6.おわりに

少しはアニメーションしやすくできたかな。

SwiftUIの出現でこれが生きるのか死ぬのかまだよくわかっていませんが、とりあえずここまで。